component — brand

Low Title

The headline set low in the frame, the signature of the system: an Outfit display title under a hairline, with one sentence and one action beside it (m) or in a footer row beneath it (xl). Sinks to the floor of a Stage with mt-auto.

  • brand
  • headline
  • hero
  • typography
  • title

use a low title when

  • The headline of a Stage — as="h1" on the first stage of a page, h2 after that.
  • size="xl" for a gallery page's title, where the headline is most of the frame.

don't

  • The title is lowercase, short, one idea, and ends with a full stop. A second sentence goes in children, not the title.
  • One action only, normally a TextLink — not a row of buttons.
  • Not a section heading inside the page body; that is a display-s heading with an Eyebrow count.

03 examples

Medium

The default: display-m under a hairline, one sentence and one action in the side column.

full-stack software development.

We build web applications end to end: database, backend and interface.

get started →

Extra large

Outfit Thin at display-xl for gallery pages; the sentence and the action move to a footer row.

full-stack software.

Two developers, Munich.

talk to us →

Title only

Both the sentence and the action are optional; the hairline and the low setting stay.

contact.

what to use instead

by job

By what the low title was doing.

A heading further down the page

source

low-title.tsx

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

import { cn } from "@/lib/utils"

/**
 * LowTitle — quebi design system
 *
 * The headline set low in the frame, the signature of the system. It takes
 * `mt-auto`, so inside a `Stage` (or any flex column) it sinks to the floor
 * and leaves the middle empty.
 *
 * - `size="m"` (default): `display-m` under a hairline, with a side column
 *   holding the one sentence and the one action.
 * - `size="xl"`: `display-xl`, Outfit Thin, for gallery pages; the sentence
 *   and the action move to a footer row under a hairline.
 *
 * The title is lowercase, short, one idea, and ends with a full stop. It is
 * lowercased by CSS too, so a title that arrives in sentence case from a CMS
 * still reads as the system wants while the source text stays intact. Give it
 * one action, normally a `TextLink` ("get started →").
 */

export interface LowTitleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
  /** The headline. Lowercase, short, ends with a full stop. */
  title: React.ReactNode
  /** `m` (default) with a side column; `xl` with a footer row. */
  size?: "m" | "xl"
  /**
   * The heading element. `h1` on the first stage of a page, `h2` (default)
   * after that — a library default of `h1` would put a second one on any page
   * that already has its own.
   */
  as?: "h1" | "h2"
  /** One call to action, normally a `TextLink`. */
  action?: React.ReactNode
  /** The one sentence that goes with the title. */
  children?: React.ReactNode
}

export function LowTitle({
  title,
  size = "m",
  as: Heading = "h2",
  action,
  children,
  className,
  ...props
}: LowTitleProps) {
  const hasAside = children != null || action != null

  if (size === "xl") {
    return (
      <div data-slot="low-title" data-size="xl" className={cn("mt-auto", className)} {...props}>
        <Heading className="m-0 font-display text-quebi-display-xl text-quebi-fg lowercase max-sm:text-quebi-display-l max-sm:font-thin">
          {title}
        </Heading>
        {hasAside && (
          <div className="mt-4.5 flex items-end justify-between gap-4 border-t border-quebi-hairline pt-3.5">
            {children != null && <p className="m-0 text-quebi-caption text-quebi-fg-muted">{children}</p>}
            {action}
          </div>
        )}
      </div>
    )
  }

  return (
    <div
      data-slot="low-title"
      data-size="m"
      className={cn(
        "mt-auto grid grid-cols-1 items-end gap-5 border-t border-quebi-hairline pt-4 sm:grid-cols-[1.3fr_1fr]",
        className,
      )}
      {...props}
    >
      <Heading
        className={cn(
          "m-0 font-display text-quebi-display-m text-quebi-fg lowercase",
          // The first stage of a page carries the larger cut on a wide screen.
          Heading === "h1" && "md:text-quebi-display-l",
        )}
      >
        {title}
      </Heading>
      {hasAside && (
        <div>
          {children != null && <p className="m-0 mb-3 text-quebi-body-s text-quebi-fg-muted">{children}</p>}
          {action}
        </div>
      )}
    </div>
  )
}