Skip to main content

Text

Atombeta@macaw-ds/ui · since 0.1.0

Renders text in a named typographic role and tone. Every string on screen goes through it; nothing else sets font size or colour.

Import

import { Text, textVariants } from '@macaw-ds/ui';

Props

PropTypeDefaultDescription
as"p" | "span" | "div" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "label" | "dt" | "dd" | "legend" | "strong" | "em" | "time"p

Rendered element. Choose for meaning: h2 for a section title, label for a field, time for a date.

Accessibility: Heading levels must not skip (h2 then h3). A label must point at a control with htmlFor.

variant"display-lg" | "display-md" | "heading-lg" | "heading-md" | "heading-sm" | "body-lg" | "body-md" | "body-sm" | "label-md" | "label-sm" | "caption" | "numeric-lg" | "numeric-md" | "numeric-sm" | "code"body-md

Typographic variant from the scale. Independent of the element, so an h2 can use heading-sm in a dense panel.

tone"primary" | "secondary" | "muted" | "inverse" | "numeric" | "success" | "warning" | "critical" | "info" | "accent"primary

Colour. primary and numeric meet 7:1; the rest meet 4.5:1 on every surface in both themes.

align"start" | "center" | "end"start

Text alignment. Money columns align end.

numericbooleanfalse

Tabular numerals plus the numeric colour (unless tone is set). Use for every number that will be compared or summed.

Accessibility: Numerals at 7:1 contrast (DECISIONS.md #8).

truncatebooleanfalse

One line with an ellipsis.

Accessibility: Truncated text must be available in full elsewhere (a title attribute or a tooltip).

childrenReactNodeAvailable balance

The text.

Slots

Named parts you pass content into.

  • children

Tokens used

Every value this component renders comes from one of these tokens.

TokenLightDark
text.size.body-md

semantic · dimension

0.875rem
0.875rem
text.leading.body-md

semantic · dimension

1.25rem
1.25rem
text.weight.body-md

semantic · fontWeight

400
400
font.family.sans

primitive · fontFamily

Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif
Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif
font.family.mono

primitive · fontFamily

'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
color.fg.primary

semantic · color

oklch(0.15 0.012 250)
oklch(0.985 0.002 250)
color.fg.secondary

semantic · color

oklch(0.38 0.012 250)
oklch(0.87 0.007 250)
color.fg.muted

semantic · color

oklch(0.48 0.012 250)
oklch(0.66 0.01 250)
color.fg.numeric

semantic · color

oklch(0.11 0.01 250)
oklch(1 0 0)
color.fg.inverse

semantic · color

oklch(1 0 0)
oklch(0.15 0.012 250)
color.status.success-fg

semantic · color

oklch(0.37 0.1 152)
oklch(0.88 0.1 152)
color.status.warning-fg

semantic · color

oklch(0.41 0.11 50)
oklch(0.9 0.12 80)
color.status.critical-fg

semantic · color

oklch(0.39 0.14 27)
oklch(0.88 0.08 27)
color.status.info-fg

semantic · color

oklch(0.38 0.09 230)
oklch(0.88 0.07 230)
color.status.accent-fg

semantic · color

oklch(0.38 0.15 262)
oklch(0.88 0.08 262)

Eject

Ejecting copies the source into your repo and records it in .macaw/ejected.json, so the fork stays visible. The same file is served as a shadcn-compatible registry item.

With the MACAW CLI
npx @macaw-ds/cli add text --eject
With shadcn
npx shadcn@latest add https://macaw-design-system.vercel.app/r/text.json

Bundle budget: 2,000 bytes gzipped, enforced by size-limit in CI.

Manifest

registry/manifests/text.json, the contract the playground, CLI, plugin and MCP server read.

{
  "name": "Text",
  "slug": "text",
  "package": "@macaw-ds/ui",
  "status": "beta",
  "since": "0.1.0",
  "level": "atom",
  "category": "typography",
  "purpose": "Renders text in a named typographic role and tone. Every string on screen goes through it; nothing else sets font size or colour.",
  "description": "The typography primitive. `variant` picks size, line height, weight and tracking from the type scale; `tone` picks the colour; `as` picks the HTML element for semantics. `numeric` switches on tabular numerals and the 7:1 numeric colour for money, counts and dates.",
  "exports": [
    "Text",
    "textVariants"
  ],
  "props": {
    "as": {
      "type": "enum",
      "values": [
        "p",
        "span",
        "div",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "label",
        "dt",
        "dd",
        "legend",
        "strong",
        "em",
        "time"
      ],
      "default": "p",
      "required": false,
      "description": "Rendered element. Choose for meaning: h2 for a section title, label for a field, time for a date.",
      "a11y": "Heading levels must not skip (h2 then h3). A label must point at a control with htmlFor.",
      "control": "select"
    },
    "variant": {
      "type": "enum",
      "values": [
        "display-lg",
        "display-md",
        "heading-lg",
        "heading-md",
        "heading-sm",
        "body-lg",
        "body-md",
        "body-sm",
        "label-md",
        "label-sm",
        "caption",
        "numeric-lg",
        "numeric-md",
        "numeric-sm",
        "code"
      ],
      "default": "body-md",
      "required": false,
      "description": "Typographic variant from the scale. Independent of the element, so an h2 can use heading-sm in a dense panel.",
      "control": "select"
    },
    "tone": {
      "type": "enum",
      "values": [
        "primary",
        "secondary",
        "muted",
        "inverse",
        "numeric",
        "success",
        "warning",
        "critical",
        "info",
        "accent"
      ],
      "default": "primary",
      "required": false,
      "description": "Colour. primary and numeric meet 7:1; the rest meet 4.5:1 on every surface in both themes.",
      "control": "select"
    },
    "align": {
      "type": "enum",
      "values": [
        "start",
        "center",
        "end"
      ],
      "default": "start",
      "required": false,
      "description": "Text alignment. Money columns align end."
    },
    "numeric": {
      "type": "boolean",
      "default": false,
      "required": false,
      "description": "Tabular numerals plus the numeric colour (unless tone is set). Use for every number that will be compared or summed.",
      "a11y": "Numerals at 7:1 contrast (DECISIONS.md #8)."
    },
    "truncate": {
      "type": "boolean",
      "default": false,
      "required": false,
      "description": "One line with an ellipsis.",
      "a11y": "Truncated text must be available in full elsewhere (a title attribute or a tooltip)."
    },
    "children": {
      "type": "node",
      "default": "Available balance",
      "required": false,
      "description": "The text."
    }
  },
  "slots": [
    "children"
  ],
  "tokens": [
    "text.size.body-md",
    "text.leading.body-md",
    "text.weight.body-md",
    "font.family.sans",
    "font.family.mono",
    "color.fg.primary",
    "color.fg.secondary",
    "color.fg.muted",
    "color.fg.numeric",
    "color.fg.inverse",
    "color.status.success-fg",
    "color.status.warning-fg",
    "color.status.critical-fg",
    "color.status.info-fg",
    "color.status.accent-fg"
  ],
  "a11y": {
    "role": "none (inherits the element's role: heading, paragraph, label, time). The HTML role attribute passes through.",
    "focusable": false,
    "keyboard": [],
    "name": "Not applicable; Text is content. For label, the name is the text and it names the control via htmlFor.",
    "notes": [
      "Pick `as` for document structure first; use `role` for size. Screen readers navigate by heading level, not by font size.",
      "Do not convey status by tone alone; pair a tone with words or an icon with a label."
    ]
  },
  "budget": {
    "gzip": 2000
  },
  "rules": [
    "All text goes through Text. Never apply text-* or font-* utilities to raw elements.",
    "Money, counts and dates use numeric (or a numeric-* role) so columns line up.",
    "Heading levels descend without skipping.",
    "label elements use htmlFor and point at the control's id."
  ],
  "dos": [
    "Use heading-sm for card titles and panel headers.",
    "Use caption with tone=\"muted\" for helper text under a field."
  ],
  "donts": [
    "Do not use display roles inside cards or tables.",
    "Do not use tone=\"muted\" for anything the user must read to act (errors, amounts)."
  ],
  "examples": [
    {
      "name": "Default",
      "props": {
        "children": "Available balance"
      }
    },
    {
      "name": "Heading",
      "props": {
        "as": "h2",
        "children": "Invoices",
        "variant": "heading-md"
      }
    },
    {
      "name": "Money",
      "description": "Tabular numerals, end aligned, numeric colour.",
      "props": {
        "numeric": true,
        "align": "end",
        "children": "$12,480.00",
        "variant": "numeric-md"
      }
    },
    {
      "name": "Status",
      "props": {
        "tone": "critical",
        "children": "Overdue by 12 days",
        "variant": "label-sm"
      }
    },
    {
      "name": "Helper",
      "props": {
        "tone": "muted",
        "children": "Updated 2 minutes ago",
        "variant": "caption"
      }
    }
  ],
  "registryDependencies": [],
  "dependencies": [
    "class-variance-authority"
  ],
  "related": [
    "badge",
    "card"
  ]
}