# Text

> All text: the type ramp as Variants, text Colour Roles, alignment and the font-scaling switch.

Component · core. Registry item `text`, shown in the vega Style. JSON: https://nativecn.dev/r/styles/vega/text.json

## Installation

```sh
npx nativecn-cli@latest add text
```

Also installs: [theme](https://nativecn.dev/docs/theme/theme).

## Usage

### Text demo

Headings, body, small and caption text in Colour Roles.

```tsx
import { View } from "react-native";

import { Text } from "@/components/text";
import { createStyles } from "@/theme";

/** The type ramp, text Colour Roles and alignment. */
export default function TextDemo() {
  const styles = useStyles();
  return (
    <View style={styles.stack}>
      <Text variant="h1">Welcome back</Text>
      <Text variant="lead" color="mutedForeground">
        Pick up where you left off.
      </Text>
      <Text>Body text is selectable by default, so people can copy it.</Text>
      <Text variant="small" color="mutedForeground">
        Last synced 2 minutes ago
      </Text>
      <Text variant="caption" color="destructive">
        Your trial ends tomorrow
      </Text>
      <Text variant="label" align="center">
        Centred label
      </Text>
    </View>
  );
}

const useStyles = createStyles((t) => ({
  stack: { gap: t.spacing[2] },
}));
```

## Props

| Prop | Type and description |
|---|---|
| `...TextProps` | React Native Text props pass through (numberOfLines, onPress, ...) |
| `variant` | "display" \| "h1" \| "h2" \| "h3" \| "h4" \| "lead" \| "body" (default) \| "label" \| "small" \| "caption" \| "button" |
| `color` | a text Colour Role: "foreground" (default), any "…Foreground" role, "primary", "destructive", "success", "warning", "info" |
| `align` | "auto" \| "left" \| "right" \| "center" \| "justify" |
| `selectable` | boolean (default true for body and small, false otherwise) |
| `allowFontScaling` | boolean (default: config.fontScaling in theme/config.ts) |
| `ref` | Ref<Text> |
| `style` | TextStyle, merged last |

## Variants

- `display`, `h1`, `h2`, `h3`, `h4`, `lead`, `body`, `label`, `small`, `caption`, `button`

## Accessibility

- display and h1–h4 have role heading, so screen readers can jump between headings.
- Font scaling follows the switch in theme/config.ts (off by default); allowFontScaling overrides it for one Text.
- When font scaling is on, the chrome Variants (label, caption, button) cap the OS font scale at 1.5×.

## Notes

The single typographic Component: use it for all text, never React Native's Text or a separate Heading. display and h1–h4 use the Heading Font; the rest use the Body Font.

## Source

### {components}/text.tsx

```tsx
import type { Ref } from "react";
import { Text as RNText, type TextProps as RNTextProps } from "react-native";

import { createStyles, useTheme, type ColorRole, type tokens } from "@/theme";

/** The type ramp: one Variant per typographic role. */
export type TextVariant = keyof typeof tokens.typeRamp;

/** Colour Roles meant for text: `foreground`, every `…Foreground`, and the status colours. */
export type TextColor =
  | "foreground"
  | Extract<ColorRole, `${string}Foreground`>
  | "primary"
  | "destructive"
  | "success"
  | "warning"
  | "info";

export type TextAlign = "auto" | "left" | "right" | "center" | "justify";

/**
 * Per-Variant behaviour. Headings get the `heading` role; reading text is selectable; chrome
 * Variants (button, label, caption) cap the OS font scale at 1.5× when font scaling is on.
 */
const variants = {
  display: { heading: true, selectable: false, chrome: false },
  h1: { heading: true, selectable: false, chrome: false },
  h2: { heading: true, selectable: false, chrome: false },
  h3: { heading: true, selectable: false, chrome: false },
  h4: { heading: true, selectable: false, chrome: false },
  lead: { heading: false, selectable: false, chrome: false },
  body: { heading: false, selectable: true, chrome: false },
  label: { heading: false, selectable: false, chrome: true },
  small: { heading: false, selectable: true, chrome: false },
  caption: { heading: false, selectable: false, chrome: true },
  button: { heading: false, selectable: false, chrome: true },
} as const satisfies Record<
  TextVariant,
  { heading: boolean; selectable: boolean; chrome: boolean }
>;

/** The OS font-scale cap for chrome Variants (ADR 0004). */
const CHROME_MAX_FONT_SCALE = 1.5;

export type TextProps = RNTextProps & {
  /** The typographic role (default `body`). */
  variant?: TextVariant;
  /** A text Colour Role (default `foreground`). */
  color?: TextColor;
  align?: TextAlign;
  ref?: Ref<RNText>;
};

const useVariantStyles = createStyles((t) => ({
  display: { ...t.type.display },
  h1: { ...t.type.h1 },
  h2: { ...t.type.h2 },
  h3: { ...t.type.h3 },
  h4: { ...t.type.h4 },
  lead: { ...t.type.lead },
  body: { ...t.type.body },
  label: { ...t.type.label },
  small: { ...t.type.small },
  caption: { ...t.type.caption },
  button: { ...t.type.button },
}));

const useColorStyles = createStyles(
  (t) =>
    ({
      foreground: { color: t.colors.foreground },
      cardForeground: { color: t.colors.cardForeground },
      popoverForeground: { color: t.colors.popoverForeground },
      primaryForeground: { color: t.colors.primaryForeground },
      secondaryForeground: { color: t.colors.secondaryForeground },
      mutedForeground: { color: t.colors.mutedForeground },
      accentForeground: { color: t.colors.accentForeground },
      destructiveForeground: { color: t.colors.destructiveForeground },
      successForeground: { color: t.colors.successForeground },
      warningForeground: { color: t.colors.warningForeground },
      infoForeground: { color: t.colors.infoForeground },
      primary: { color: t.colors.primary },
      destructive: { color: t.colors.destructive },
      success: { color: t.colors.success },
      warning: { color: t.colors.warning },
      info: { color: t.colors.info },
    }) satisfies Record<TextColor, { color: string }>,
);

const useAlignStyles = createStyles(() => ({
  auto: { textAlign: "auto" },
  left: { textAlign: "left" },
  right: { textAlign: "right" },
  center: { textAlign: "center" },
  justify: { textAlign: "justify" },
}));

/**
 * All text in the app. Applies the type ramp and a text Colour Role, and honours the Theme's
 * font-scaling switch (`config.fontScaling`, off by default). React Native Text props pass
 * through; `style` is merged last.
 */
export function Text({
  variant = "body",
  color = "foreground",
  align,
  selectable,
  allowFontScaling,
  maxFontSizeMultiplier,
  role,
  style,
  ...props
}: TextProps) {
  const { config } = useTheme();
  const variantStyles = useVariantStyles();
  const colorStyles = useColorStyles();
  const alignStyles = useAlignStyles();
  const v = variants[variant];
  const scaling = allowFontScaling ?? config.fontScaling;

  return (
    <RNText
      role={role ?? (v.heading ? "heading" : undefined)}
      selectable={selectable ?? v.selectable}
      allowFontScaling={scaling}
      maxFontSizeMultiplier={
        maxFontSizeMultiplier ?? (scaling && v.chrome ? CHROME_MAX_FONT_SCALE : undefined)
      }
      style={[variantStyles[variant], colorStyles[color], align ? alignStyles[align] : null, style]}
      {...props}
    />
  );
}
```
