Get started

Utils

Working with Variants

Headline Component (Tailwind / Uno)

An example of an headline component that fully utilizes the features of variants and Tailwind / Uno classes.

Intro

Consistent typography is key to a cohesive user interface in web development. The Headline component streamlines the creation and management of headline elements in React projects by leveraging react-classmate. It supports rendering standard HTML heading tags (h1 to h6) as well as other elements like strong, span, p, and div.

With predefined styling variants and preset components (H1Headline, H2Headline, etc.), the Headline component ensures uniform styling and reduces repetitive code. This approach improves maintainability and simplifies the implementation of design changes across your application. Whether you're building a blog, an e-commerce site, or another type of web application, the Headline component offers a practical solution for managing typography efficiently.

Idea and Example

With the Convenience Components, you can easily create headlines with different levels and elements.

Full Component

In order to work with the visual classes, you will need to install tailwind or uno-css (windi/tw preset) alongside the react-classmate. We'll walk through all the important bits after this section

import type { HTMLAttributes } from "react"
import { type RcBaseComponent, type VariantsConfig, createVariantMap } from "react-classmate"

// 1. Setup Accepted Elements and Types

const headlineLevels = ["h1", "h2", "h3", "h4", "h5", "h6"] as const
type HeadlineLevels = (typeof headlineLevels)[number]

const additionalElements = ["strong", "span", "p", "div"] as const
type AdditionalHeadlineTypes = (typeof additionalElements)[number]

type RcVariantType = HeadlineLevels | "blank"

// 2. Setup Variants

interface HeadlineVariantProps extends HTMLAttributes<HTMLElement> {
  $as?: RcVariantType
}

const headlineVariants: VariantsConfig<HeadlineVariantProps, HeadlineVariantProps> = {
  base: "",
  variants: {
    $as: {
      h1: "text-3xl md:text-4xl font-bold",
      h2: "text-2xl md:text-3xl font-bold"
      h3: "text-xl md:text-2xl font-bold",
      h4: "text-lg md:text-xl font-bold",
      h5: "text-lg font-bold",
      h6: "text-base font-bold",
      blank: "",
    },
  },
}

// 3. Create Variant Map

const hVariantMap: Record<string, RcBaseComponent<HeadlineVariantProps>> = createVariantMap({
  elements: [...additionalElements, ...headlineLevels],
  variantsConfig: headlineVariants,
})

// 4. Define the React Component

interface HeadlineProps extends HTMLAttributes<HTMLElement> {
  variant?: RcVariantType
  as: Exclude<RcVariantType, "blank"> | AdditionalHeadlineTypes
}

const Headline = ({ as, variant, ...props }: HeadlineProps) => {
  const isHeadline = headlineLevels.includes(as as HeadlineLevels)
  const variantToUse = variant || (isHeadline ? (as as RcVariantType) : "blank")

  const Component = hVariantMap[as] || null

  return (
    <Component $as={variantToUse} {...props}>
      {props.children}
    </Component>
  )
}

// 5. Create Convenience Components

type HeadlineComponentProps = { as?: HeadlineProps["as"] } & HTMLAttributes<HTMLElement>
const createHeadlineComponent = (level: HeadlineLevels) => {
  return ({ as = level, ...props }: HeadlineComponentProps) => (
    <Headline as={as} {...(as !== level ? { variant: level } : {})} {...props} />
  )
}

// 6. Export(s)

export const H1Headline = createHeadlineComponent("h1")
export const H2Headline = createHeadlineComponent("h2")
export const H3Headline = createHeadlineComponent("h3")
export const H4Headline = createHeadlineComponent("h4")
export const H5Headline = createHeadlineComponent("h5")
export const H6Headline = createHeadlineComponent("h6")
export default Headline

Step by Step

1. Setup accepted elements and types

We define the set of HTML elements that the Headline component can render. This includes standard heading levels (h1 to h6) and additional elements such as strong, span, p, and div. By specifying these, we ensure that the Headline component is flexible and can accommodate various HTML tags based on the provided props.

const headlineLevels = ["h1", "h2", "h3", "h4", "h5", "h6"] as const
type HeadlineLevels = (typeof headlineLevels)[number]

const additionalElements = ["strong", "span", "p", "div"] as const
type AdditionalHeadlineTypes = (typeof additionalElements)[number]

type RcVariantType = HeadlineLevels | "blank"

Additionally, we introduce a custom variant type RcVariantType that includes both the headline levels and a "blank" variant. The "blank" variant allows the component to render non-heading elements without applying any specific headline styles.

2. Setup Variants

Here, we configure the styling variants for each headline level using the VariantsConfig type from react-classmate. This configuration maps each variant to specific CSS classes or dynamic styles.

interface HeadlineVariantProps extends HTMLAttributes<HTMLElement> {
  $as?: RcVariantType
}

const headlineVariants: VariantsConfig<HeadlineVariantProps, HeadlineVariantProps> = {
  base: "",
  variants: {
    $as: {
      h1: "text-3xl md:text-4xl font-bold",
      h2: "text-2xl md:text-3xl font-bold"
      h3: "text-xl md:text-2xl font-bold",
      h4: "text-lg md:text-xl font-bold",
      h5: "text-lg font-bold",
      h6: "text-base font-bold",
      blank: "",
    },
  },
}

This allows you to use the usual Variants and CSS-in-JS (style) syntax to define the styles for each headline level.

3. Create Variant Map

This section utilizes the createVariantMap function to generate a mapping between the defined elements and their corresponding variant configurations. The hVariantMapobject serves as a lookup table, allowing the Headline component to select the appropriate styles based on the element type (as prop) provided. By merging additionalElements andheadlineLevels, we ensure that all supported elements are accounted for in the variant map.

const hVariantMap: Record<string, RcBaseComponent<HeadlineVariantProps>> = createVariantMap({
  elements: [...additionalElements, ...headlineLevels],
  variantsConfig: headlineVariants,
})

4. Define the React Component

We define the main Headline component here, which is responsible for rendering the appropriate HTML element with the correct styling based on the provided props. The component determines whether the as prop corresponds to a headline level or an additional element.

It then selects the appropriate variant to apply. If no variant is specified, it defaults to the variant matching the as prop for headline elements or uses the "blank" variant for additional elements. This design ensures flexibility and reusability across different parts of your application.

interface HeadlineProps extends HTMLAttributes<HTMLElement> {
  variant?: RcVariantType
  as: Exclude<RcVariantType, "blank"> | AdditionalHeadlineTypes
}

const Headline = ({ as, variant, ...props }: HeadlineProps) => {
  const isHeadline = headlineLevels.includes(as as HeadlineLevels)
  const variantToUse = variant || (isHeadline ? (as as RcVariantType) : "blank")

  const Component = hVariantMap[as] || null

  return (
    <Component $as={variantToUse} {...props}>
      {props.children}
    </Component>
  )
}

5. Create Convenience Components

To simplify the usage of different headline levels, we create convenience components like H1Headline, H2Headline, etc. These components preset the as prop to a specific headline level, reducing the need to specify it manually each time. Additionally, if a different as prop is provided, the variant adjusts accordingly to maintain consistent styling.

The refactored createHeadlineComponent function now uses an inline type definition for props, combining as with all HTML attributes. This approach reduces redundancy and makes the code more concise.

type HeadlineComponentProps = { as?: HeadlineProps["as"] } & HTMLAttributes<HTMLElement>
const createHeadlineComponent = (level: HeadlineLevels) => {
  return ({ as = level, ...props }: HeadlineComponentProps) => (
    <Headline as={as} {...(as !== level ? { variant: level } : {})} {...props} />
  )
}

6. Export(s)

Finally, we export the convenience headline components for external use. Each exported component corresponds to a specific headline level, allowing for straightforward integration into your application's UI. The default export is the main Headline component, providing maximum flexibility for custom use cases.

export const H1Headline = createHeadlineComponent("h1")
export const H2Headline = createHeadlineComponent("h2")
export const H3Headline = createHeadlineComponent("h3")
export const H4Headline = createHeadlineComponent("h4")
export const H5Headline = createHeadlineComponent("h5")
export const H6Headline = createHeadlineComponent("h6")
export default Headline