Heading
A semantic typography component that bridges the gap between design flexibility and HTML structure. It follows the Shadcn/ui philosophy: clean code, predictable behavior, and high customizability.
Author's Note
The Heading component was born out of a need for a more disciplined yet flexible approach to web typography. In many projects, I've seen semantic hierarchy sacrificed for visual styling, or vice versa. By decoupling the visual size from the HTML tag while enforcing sensible defaults like text-balance and tracking-tight, this component ensures that every headline in your app is not only SEO-friendly and accessible but also carries a premium, polished aesthetic without the manual overhead of repetitive Tailwind utility classes.
import type * as React from "react";
import { Heading } from "@/components/ui/heading";
export function HeadingDemo(): React.ReactElement {
return (
<div className="flex flex-col gap-2">
<Heading size="h1">Heading</Heading>
<Heading size="h2">Heading</Heading>
<Heading size="h3">Heading</Heading>
<Heading size="h4">Heading</Heading>
<Heading size="h5">Heading</Heading>
<Heading size="h6">Heading</Heading>
</div>
);
}Installation
CLI
npx shadcn@latest add @aloeui/headingManual
Install the following dependencies:
npm install @radix-ui/react-slot class-variance-authorityCopy and paste the following code into your project.
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
const headingVariants = cva("", {
variants: {
size: {
h1: "text-4xl md:text-5xl lg:text-6xl",
h2: "text-3xl md:text-4xl lg:text-5xl",
h3: "text-2xl md:text-3xl lg:text-4xl",
h4: "text-xl md:text-2xl lg:text-3xl",
h5: "text-lg md:text-xl lg:text-2xl",
h6: "text-base md:text-lg lg:text-xl",
},
weight: {
normal: "font-normal",
medium: "font-medium",
semibold: "font-semibold",
bold: "font-bold",
},
muted: {
false: "text-foreground",
true: "text-muted-foreground",
},
tracking: {
tight: "tracking-tight",
normal: "tracking-normal",
wide: "tracking-wide",
},
leading: {
none: "leading-none",
tight: "leading-tight",
snug: "leading-snug",
normal: "leading-normal",
},
balance: {
true: "text-balance",
false: "",
},
truncate: {
true: "truncate",
false: "",
},
},
defaultVariants: {
size: "h1",
weight: "bold",
muted: false,
tracking: "tight",
leading: "tight",
balance: true,
truncate: false,
},
});
const Heading = React.forwardRef<
HTMLHeadingElement,
React.ComponentPropsWithoutRef<"h1"> &
VariantProps<typeof headingVariants> & { asChild?: boolean }
>(
(
{
className,
size = "h1",
weight,
muted,
tracking,
leading,
balance,
truncate,
asChild = false,
...props
},
ref,
) => {
const Comp = asChild ? Slot : (size ?? "h1");
return (
<Comp
data-slot="heading"
ref={ref}
className={cn(
headingVariants({
size,
weight,
muted,
tracking,
leading,
balance,
truncate,
className,
}),
)}
{...props}
/>
);
},
);
Heading.displayName = "Heading";
export { Heading, headingVariants };Examples
Size
Choose the appropriate size (h1–h6).
import type * as React from "react";
import { Heading } from "@/components/ui/heading";
export function HeadingSizeDemo(): React.ReactElement {
return (
<div className="flex flex-col gap-2">
<Heading size="h1">Heading</Heading>
<Heading size="h2">Heading</Heading>
<Heading size="h3">Heading</Heading>
<Heading size="h4">Heading</Heading>
<Heading size="h5">Heading</Heading>
<Heading size="h6">Heading</Heading>
</div>
);
}Weight
Control font weight with the weight prop.
import type * as React from "react";
import { Heading } from "@/components/ui/heading";
export function HeadingWeightDemo(): React.ReactElement {
return (
<div className="flex flex-col gap-2">
<Heading size="h3" weight="normal">
Heading
</Heading>
<Heading size="h3" weight="medium">
Heading
</Heading>
<Heading size="h3" weight="semibold">
Heading
</Heading>
<Heading size="h3" weight="bold">
Heading
</Heading>
</div>
);
}Muted
Use muted for secondary headings.
Heading
Heading
import type * as React from "react";
import { Heading } from "@/components/ui/heading";
export function HeadingMutedDemo(): React.ReactElement {
return (
<div className="flex flex-col gap-2">
<Heading size="h3">Heading</Heading>
<Heading size="h3" muted>
Heading
</Heading>
</div>
);
}Tracking & Leading
Adjust letter spacing and line height.
Heading
Heading
Heading
import type * as React from "react";
import { Heading } from "@/components/ui/heading";
export function HeadingTrackingDemo(): React.ReactElement {
return (
<div className="flex flex-col gap-2">
<Heading size="h2" tracking="tight" leading="tight">
Heading
</Heading>
<Heading size="h2" tracking="normal" leading="normal">
Heading
</Heading>
<Heading size="h2" tracking="wide" leading="normal">
Heading
</Heading>
</div>
);
}Balance & Truncate
balance(default: true) — enablestext-balancefor multi-line headings.truncate— truncates with ellipsis when overflow.
import type * as React from "react";
import { Heading } from "@/components/ui/heading";
export function HeadingBalanceTruncateDemo(): React.ReactElement {
return (
<div className="flex flex-col gap-4">
<div>
<span className="text-muted-foreground mb-2 block text-sm">
balance (default: true)
</span>
<Heading size="h4" balance={false}>
Heading
</Heading>
</div>
<div>
<span className="text-muted-foreground mb-2 block text-sm">
truncate
</span>
<div className="w-48">
<Heading size="h4" truncate>
Heading
</Heading>
</div>
</div>
</div>
);
}asChild
Use asChild to merge props with your child element (e.g. Link, motion.div).
Use asChild to merge props with your child element.
import type * as React from "react";
import { Heading } from "@/components/ui/heading";
export function HeadingAsChildDemo(): React.ReactElement {
return (
<div className="flex flex-col gap-2">
<Heading asChild size="h2">
<a href="#">Heading as Link</a>
</Heading>
<p className="text-muted-foreground text-sm">
Use <code>asChild</code> to merge props with your child element.
</p>
</div>
);
}API Reference
Heading
Prop
Type
Introduction
Copy-paste ready Shadcn/ui components
Paragraph
A foundational typography component optimized for legibility and rhythmic flow. It streamlines body text management by providing a standardized scale for sizes and line-heights, ensuring your content remains perfectly readable across all screen sizes while adhering to the Shadcn/ui design principles.