Brand

Section heading

Eyebrow, framed display title and a line of lead.

stablev0.1.0 Chromium 103 ui-scale safe

Install

Copies the file into components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.
bunx shadcn@3 add @fastrp/section-heading

Examples

Default

Open alone
Sezon 3

Kendi hikâyeni yaz

Bir meslek seç, bir çeteye katıl ya da kendi işini kur. Şehir senin yaptıklarınla şekillenir.

Fast Roleplay

API

<SectionHeading />

The heading a section opens with: a small eyebrow, the title in the display voice framed by corner ticks, and an optional line of description. Wrap a word of the title in `<em>` to set it in the accent.

PropTypeDefaultNotes
title*ReactNode—
align"center" | "start"start
as"h1" | "h2" | "h3"h2
classNamestring—
descriptionReactNode—
eyebrowReactNode—
framedbooleantrueFrame the title in corner ticks. On by default — the brand's heading.
size"sm" | "md" | "lg" | "xl"—
variant"outline" | "solid"—

Dependencies

npm

  • class-variance-authority@^0.7.1

Registry

Source

components/ds/section-heading.tsxShow
import { cva, type VariantProps } from 'class-variance-authority';
import type { ReactNode } from 'react';
import { Bracketed } from '@/components/ds/bracketed';
import { cn } from '@/lib/utils';

// @fast-ds [email protected]

const titleVariants = cva(
  // `em` inside a title is the brand emphasis: upright, in the accent — never italic.
  'font-display text-foreground [&_em]:not-italic [&_em]:text-brand',
  {
    variants: {
      size: {
        sm: 'text-3xl md:text-4xl',
        md: 'text-4xl md:text-6xl',
        lg: 'text-5xl md:text-display-2',
        xl: 'text-5xl md:text-7xl lg:text-display-1',
      },
      variant: {
        solid: '',
        /** The web site's outlined twin: a stroke and no fill. */
        outline: 'text-outline',
      },
    },
    defaultVariants: { size: 'md', variant: 'solid' },
  },
);

export type SectionHeadingProps = VariantProps<typeof titleVariants> & {
  title: ReactNode;
  eyebrow?: ReactNode;
  description?: ReactNode;
  /** Frame the title in corner ticks. On by default — the brand's heading. */
  framed?: boolean;
  align?: 'start' | 'center';
  as?: 'h1' | 'h2' | 'h3';
  className?: string;
};

/**
 * The heading a section opens with: a small eyebrow, the title in the display voice framed by
 * corner ticks, and an optional line of description. Wrap a word of the title in `<em>` to set it
 * in the accent.
 */
export function SectionHeading({
  title,
  eyebrow,
  description,
  framed = true,
  align = 'start',
  as: Heading = 'h2',
  size,
  variant,
  className,
}: SectionHeadingProps) {
  const heading = (
    <Heading className={cn(titleVariants({ size, variant }), 'leading-[0.95]')}>{title}</Heading>
  );

  return (
    <div
      className={cn(
        'flex flex-col gap-4',
        align === 'center' && 'items-center text-center',
        className,
      )}
    >
      {eyebrow && <span className="text-eyebrow uppercase text-muted-foreground">{eyebrow}</span>}
      {framed ? <Bracketed>{heading}</Bracketed> : heading}
      {description && (
        <p className="max-w-2xl text-base leading-relaxed text-muted-foreground md:text-lg">
          {description}
        </p>
      )}
    </div>
  );
}