Skip to content
View as Markdown

Text ​

The Text component applies a typography role to its content. A role sets font size, line height, font weight, and letter spacing together. The role does not set the color, and it does not choose the HTML element. Use the render prop to choose the element (Base UI render pattern).

Import ​

tsx
import { Text } from "@tailor-platform/app-shell";

Basic Usage ​

tsx
function BasicUsage() {
  return (
    <div className="flex flex-col gap-1">
      <Text variant="heading-md">Purchase orders</Text>
      <Text className="text-muted-foreground">Orders sent to suppliers this month.</Text>
    </div>
  );
}

Props ​

Generated from the type surface — first-party props only.

TextProps ​

PropTypeDefaultDescription
ref?React.Ref<HTMLElement>-Ref to the rendered element.
render?React.ReactElement-Custom element to render as (Base UI render prop). Use it to set the heading level, for example render={<h3 />}.
variant?"heading-lg" | "heading-md" | "heading-sm" | "body-md" | "body-sm" | "label-md" | "label-sm" | "code-sm" | "body-md-relaxed" | "body-sm-relaxed" | null"body-md"Typography role. Sets font size, line height, font weight, and letter spacing together.

Exports ​

  • Text — function
  • TextProps — type

All standard HTML attributes are also accepted. Put an attribute that belongs to one element, such as htmlFor on a label, on the render element:

tsx
<Text variant="label-md" render={<label htmlFor="quantity" />}>
  Quantity
</Text>

Some lint rules report this example; see Lint.

Roles ​

variant takes one of the ten roles. Each role renders a default element when you do not pass render:

variantDefault elementUse
heading-lgh2Page title (Layout.Header)
heading-mdh2Section heading, card title
heading-smh2Sub-section heading
body-mdpBody copy, descriptions (default role)
body-smpCaption, timestamp, metadata
body-md-relaxedpLong text, such as a chat message or a document
body-sm-relaxedpLong text at the small size
label-mdspanField label, short UI text, figures in a table
label-smspanSmall label, badge text
code-smspanID and code. Text adds font-mono for you
tsx
function Roles() {
  return (
    <div className="flex flex-col gap-3">
      {/* The headings render a div so that this specimen is not a document heading. */}
      <Text variant="heading-lg" render={<div />}>
        heading-lg: Purchase orders
      </Text>
      <Text variant="heading-md" render={<div />}>
        heading-md: Line items
      </Text>
      <Text variant="heading-sm" render={<div />}>
        heading-sm: Shipping address
      </Text>
      <Text variant="body-md">body-md: Orders sent to suppliers this month.</Text>
      <Text variant="body-sm">body-sm: Updated 2 hours ago</Text>
      <Text variant="label-md">label-md: Total amount</Text>
      <Text variant="label-sm">label-sm: Draft</Text>
      <Text variant="code-sm">code-sm: PO-2026-0001</Text>
    </div>
  );
}

The sizes and line heights are in the Typography section of the styling guide.

Heading Level ​

All three heading roles render h2 by default. The heading level comes from the document structure, not from the visual size, so set it with render:

tsx
<Text variant="heading-md" render={<h3 />}>
  Line items
</Text>

To use a heading role for text that is not a heading, render a div or a span:

tsx
<Text variant="heading-md" render={<div />}>
  1,240 orders
</Text>

Lint ​

Some jsx-a11y rules check that an element has text inside it, for example heading-has-content for <h3 /> and label-has-associated-control for <label />. These rules report the element in render as empty, because they do not see that Text passes its children to that element. If your app enables them, disable the rule on that line. ESLint and oxlint both read this comment:

tsx
// eslint-disable-next-line jsx-a11y/heading-has-content -- Text renders the children
<Text variant="heading-md" render={<h3 />}>
  Line items
</Text>

Color and Modifiers ​

Set the color with a color token in className. Four modifiers may also be combined with a role: leading-none for single-line text, tabular-nums, font-semibold for emphasis, and uppercase.

tsx
function ColorAndModifiers() {
  return (
    <div className="flex flex-col gap-3">
      <Text variant="body-md" className="text-muted-foreground">
        Orders sent this month.
      </Text>
      <Text variant="label-md" className="tabular-nums">
        1,240.00
      </Text>
    </div>
  );
}
tsx
<Text variant="body-md" className="text-muted-foreground">
  Orders sent this month.
</Text>
<Text variant="heading-md" render={<h3 />} className="leading-none">
  Card title
</Text>
<Text variant="label-md" className="tabular-nums">
  1,240.00
</Text>

Do not combine a role with other leading-*, tracking-*, or text-* size values. Pick a different variant instead. A text-* size class such as text-sm has no effect on Text: AppShell's stylesheet loads after yours, so the role's size wins.

Using Roles Without Text ​

Each role is also a plain utility, for example text-heading-md. Use the utility when you style your own element and do not need Text:

tsx
<th className="text-label-md text-muted-foreground">Amount</th>

TypeScript ​

typescript
import { type TextProps } from "@tailor-platform/app-shell";

Accessibility ​

  • A heading role renders a real heading (h2) by default, so screen readers list it with the other headings. Set the level with render so the levels follow the page structure.
  • Do not choose a heading level for its size. Text keeps the size and the level separate.
  • body-* roles render p. Inside another p, render a span instead (<Text render={<span />}>): the browser closes the outer p before the inner one, so React warns and server rendering fails to hydrate. Inside inline elements such as button, label or span, a p is invalid HTML too, so render a span there as well.

Palettes ​

The cream and bloom palettes set the line height and letter spacing of h1 to h6 elements. Under these palettes, a heading role on a heading element uses the palette values (line height 1.2, letter spacing -0.03em), including when you add leading-none. The same role on a div or span uses the role values. See the Typography section of the styling guide.

  • Card - The table above lists heading-md for card titles
  • Layout - The table above lists heading-lg for the page title

AppShell's own components, including these two, still use the stock utilities. They will move to the roles in a later change.