Working with Variants
An example of an headline component that fully utilizes the features of variants
and Tailwind / Uno classes.
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.
With the Convenience Components, you can easily create headlines with different levels and elements.
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
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.
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.
This section utilizes the createVariantMap
function to generate a mapping between the defined elements and their corresponding variant configurations. The hVariantMap
object 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,
})
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>
)
}
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} />
)
}
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