Text

Body copy and inline text with a fixed set of sizes, weights, and semantic colors.

Basic usage

Install the package:

pnpm add @design-schema/react @design-schema/tokens

Import the stylesheet once for the app, then the component:

import '@design-schema/tokens/calm-precise/css';
import '@design-schema/react/index.css';
import { Text } from '@design-schema/react';

Examples

Default

  • Use the email you signed up with.

    Default

    16px / 1rem · font.size.md

    400 · font.weight.regular

    #2a2b2f · #ecedee · #2d2b27 · #edddc0 · color.foreground

  • This story frames the component in a decorator — a background, a width — that this page does not run, so it is shown as source rather than rendered without its frame.

    Truncate Inline

    16px / 1rem · font.size.md

    400 · font.weight.regular

    #2a2b2f · #ecedee · #2d2b27 · #edddc0 · color.foreground

  • Changes are saved automatically. You can undo any change for 30 days.

    Body Copy

    16px / 1rem · font.size.md

    400 · font.weight.regular

    #2a2b2f · #ecedee · #2d2b27 · #edddc0 · color.foreground

  • Last updated 2 minutes ago.

    Caption

    11px / 0.6875rem · 10px / 0.625rem · font.size.xs

    400 · font.weight.regular

    #56585c · #bcbec1 · #605747 · #ccbc9f · color.foreground.muted

  • Error: enter an email address like [email protected]
    Inline Error Wording

    16px / 1rem · font.size.md

    400 · font.weight.regular

    #b51d26 · #ff958d · color.foreground.danger

size

  • Use the email you signed up with.

    size="xs"

    11px / 0.6875rem · 10px / 0.625rem · font.size.xs

  • Use the email you signed up with.

    size="sm"

    13px / 0.8125rem · font.size.sm

  • Use the email you signed up with.

    size="md"

    16px / 1rem · font.size.md

  • Use the email you signed up with.

    size="lg"

    19px / 1.1875rem · 20px / 1.25rem · font.size.lg

  • Use the email you signed up with.

    size="xl"

    23px / 1.4375rem · 25px / 1.5625rem · font.size.xl

weight

  • Use the email you signed up with.

    weight="regular"

    400 · font.weight.regular

  • Use the email you signed up with.

    weight="medium"

    500 · font.weight.medium

  • Use the email you signed up with.

    weight="semibold"

    600 · font.weight.semibold

  • Use the email you signed up with.

    weight="bold"

    700 · font.weight.bold

tone

  • Use the email you signed up with.

    tone="default"

    #2a2b2f · #ecedee · #2d2b27 · #edddc0 · color.foreground

  • Use the email you signed up with.

    tone="strong"

    #000000 · #ffffff · #040302 · #f9eace · color.foreground.strong

  • Use the email you signed up with.

    tone="muted"

    #56585c · #bcbec1 · #605747 · #ccbc9f · color.foreground.muted

  • Error: enter an email address like [email protected]

    tone="danger"

    #b51d26 · #ff958d · color.foreground.danger

  • This story frames the component in a decorator — a background, a width — that this page does not run, so it is shown as source rather than rendered without its frame.

    tone="onAction"

    #ffffff · #f9eace · color.foreground.onAction

align

  • Use the email you signed up with.

    align="start"
  • Use the email you signed up with.

    align="center"
  • Use the email you signed up with.

    align="end"

element

  • Use the email you signed up with.

    element="p"
  • Use the email you signed up with.
    element="span"

truncate

  • This story frames the component in a decorator — a background, a width — that this page does not run, so it is shown as source rather than rendered without its frame.

    truncate
  • This story frames the component in a decorator — a background, a width — that this page does not run, so it is shown as source rather than rendered without its frame.

    truncate

Props

7 rows
PropTypeDefaultDescriptionPlatforms
children requiredcontent—

The text content. Inline formatting (emphasis, links) is allowed; block elements are not.

web, lit, rn, swiftui
sizexs | sm | md | lg | xlmd

Maps to the font size scale. `md` is body copy; `xs` is the smallest readable size and is reserved for captions and metadata.

web, lit, rn, swiftui
weightregular | medium | semibold | boldregular

Emphasis without changing size. Prefer weight over color for hierarchy.

web, lit, rn, swiftui
tonedefault | strong | muted | danger | onActiondefault

Semantic color. `onAction` is only for text placed on an action background, and its story paints that background (color.action.primary.background) behind the Text, on a wrapper holding a Box with `inset: md` around it. No component paints an action surface, so that wrapper is sanctioned story scaffolding like the truncate width: a plain element (a `div` on web and Lit, a View on native) whose background is the token itself. Enum stories use the Default children, "Use the email you signed up with.", except `ToneDanger`, which uses the `inline-error-wording` children verbatim on every platform so colour is never the only signal, and `ToneOnAction`, which reads "Text on an action background". The web modifier class is kebab-case (`ds-text--tone-on-action`), from an explicit tone-to-class table. There is no `inverse` tone: the shared foreground vocabulary has no such name, so an inverse surface re-scopes the foreground instead (see `styles.color`).

Accessibility: Every tone meets 4.5:1 on the page background in every theme and mode except onAction, which is checked against color.action.primary.background.

web, lit, rn, swiftui
alignstart | center | endstart

Horizontal alignment. `start`/`end` follow writing direction. Native has no logical text alignment, so they resolve through I18nManager.isRTL at render; a direction change mid-session does not re-align text that is already on screen. Web and Lit use CSS `text-align: start|end`, which follows `dir` live.

web, lit, rn, swiftui
truncatebooleanfalse

Clip to one line with an ellipsis. On web the full text is exposed via `title` when children is a plain string; otherwise the consumer passes `title`, and neither one is a development warning — the text is then reachable only to a screen reader. A consumer `title` always wins and is forwarded unchanged, with or without `truncate`; an explicit `title={undefined}` counts as not passed, so the string children still supply it. On Lit there is no `title` property (the name is not attribute-safe): a `title` attribute the consumer puts on the host is observed and copied unchanged to the `part="text"` element, where it wins the same way, and is left on the host as well (Text never edits the consumer's DOM). Web and Lit therefore differ for rich children on purpose: web gets no automatic `title` unless children is a plain string, while Lit's textContent title covers slotted markup too. With no consumer title there is no "plain string" state, so `title` comes from the host's flattened, whitespace-collapsed textContent, is omitted when that is empty, sits on the `part="text"` element (the one that clips), and follows live edits to the text (a MutationObserver over the host's subtree, since slotchange misses character changes). With `element: span` the clipped box is `display: inline-block; max-inline-size: 100%; vertical-align: bottom`, so the width comes from the parent and the clipped box stays on the line; on the default `p` the clipped box stays `display: block`. A `TruncateInline` story (truncate with `element: span`) shows that case on web and Lit. Native clips with `numberOfLines={1}` and `ellipsizeMode="tail"` and has no affordance that reveals the rest — a known gap. Truncate stories need a width to clip against; that decorator (or render wrapper, which adds no args) is story scaffolding, not a binding, and may use a literal (`max-inline-size: 24ch` on web and Lit, `width: 200` on native).

Accessibility: Truncated text is still read in full by screen readers; ensure sighted users can also reach it.

web, lit, rn, swiftui
elementp | spanp

The HTML element to render — `p` for a block, `span` for inline. Labels and legends are native elements rendered by Input and Fieldset, which own the association; Text never renders one.

web, lit

Accessibility

Implicit ARIA role: generic.

Anatomy

text

What generation guarantees

Contrast — Text and meaningful non-text contrast meets WCAG AA; the pairs are listed below.

Contrast pairs

4 rows
ForegroundBackgroundLevel
color.foregroundcolor.backgroundAA
color.foreground.strongcolor.backgroundAA
color.foreground.mutedcolor.backgroundAA
color.foreground.dangercolor.backgroundAA

This component does not animate, so it has no reduced-motion behaviour to describe.