AloeUI
Components

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.

API

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/heading

Manual

Install the following dependencies:

npm install @radix-ui/react-slot class-variance-authority

Copy 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) — enables text-balance for 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).

Heading as Link

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

On this page