npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fvc/typography

v4.0.0

Published

`@fvc/typography` provides text rendering primitives on top of Ant Design's `Typography` component. It extends the Ant Design API with design-system props for font family, size, weight, line height, color, alignment, and underline — all applied via CSS cu

Readme

@fvc/typography

@fvc/typography provides text rendering primitives on top of Ant Design's Typography component. It extends the Ant Design API with design-system props for font family, size, weight, line height, color, alignment, and underline — all applied via CSS custom properties so they compose cleanly with parent themes. Sub-components cover every common text role: Title, Text, Paragraph, and Link.

Text and Paragraph additionally support inline editing with a design-system action bar — see Editable Mode.

Installation

bun add @fvc/typography

Peer Dependencies

bun add react antd @fvc/utils @fvc/icons

@fvc/icons is required: the editable action bar renders Icon.Mode, Icon.CheckCircleOutline, Icon.Block, and Icon.DeleteOutline from it.

| Peer | Range | | ------------ | --------- | | react | ^18.0.0 | | antd | ^5.0.0 | | @fvc/utils | ^3.0.3 | | @fvc/icons | ^1.0.0 |

Import

import { Typography } from '@fvc/typography';

Typography is the only runtime export. Types are available from the ./types subpath — see TypeScript.

Quick Start

import { Typography } from '@fvc/typography';

export function Hero() {
  return (
    <>
      <Typography.Title>Welcome</Typography.Title>
      <Typography.Text>Subtitle text here</Typography.Text>
    </>
  );
}

Components

| Component | Renders | Notes | | ------------------------ | ---------------------------------- | ------------------------------------------------ | | <Typography> | <article class="fvc-typography"> | Block container for mixed text content. | | <Typography.Title> | <h1>–<h5> | level selects the tag. level={4} uppercases. | | <Typography.Text> | <span> | Inline text. Supports editable. | | <Typography.Paragraph> | <div> | Block text. Supports editable. | | <Typography.Link> | <a> | Own size scale — see Link. |

Common Usage

Typography (root)

Wraps a block of mixed content. Useful when you want the design-system font stack and color applied to a region without picking a specific text role for it.

<Typography>
  <Typography.Title level={3}>Release notes</Typography.Title>
  <Typography.Paragraph>Shipped this week.</Typography.Paragraph>
</Typography>

Title

<Typography.Title>Page heading</Typography.Title>
<Typography.Title level={2}>Section heading</Typography.Title>

level={4} renders with text-transform: uppercase by default — a deliberate design-system rule.

Text

<Typography.Text>Body copy</Typography.Text>
<Typography.Text strong>Bold</Typography.Text>
<Typography.Text italic>Italic</Typography.Text>
<Typography.Text code>inline code</Typography.Text>

strong, italic, code, mark, delete, keyboard, ellipsis, copyable and the rest of the Ant Design surface pass straight through.

Paragraph

<Typography.Paragraph>
  A longer block of text that wraps across multiple lines.
</Typography.Paragraph>

Link

<Typography.Link href="https://example.com">Visit site</Typography.Link>

Link has its own size scale and its own defaults — size="medium", weight="700", font="body":

<Typography.Link size="small" href="/docs">Small link</Typography.Link>
<Typography.Link size="medium" href="/docs">Medium link (default)</Typography.Link>

| size | Font size token | Line height token | | ---------- | ------------------------- | ------------------------- | | 'small' | --typography-link-sm-fz | --typography-link-sm-lh | | 'medium' | --typography-link-md-fz | --typography-link-md-lh |

Any other value — a number or a px string — bypasses the named scale and is applied directly, exactly as size behaves on the other sub-components:

<Typography.Link size={18} lineHeight={26} href="/docs">
  Custom size
</Typography.Link>

Links are underlined by default and switch to --typography-link-hover-color on hover. disabled links use --typography-link-disabled-color and set pointer-events: none.

Font Size and Weight

<Typography.Text size={14} weight="600">
  Semi-bold small text
</Typography.Text>

Custom Color

<Typography.Text color="#e74c3c">Error message</Typography.Text>

Font Family

The font prop selects a typographic role, not a typeface:

<Typography.Text font="heading" weight="700">
  Heading text
</Typography.Text>
<Typography.Text font="body">Body copy</Typography.Text>

'heading' resolves to --fvc-font-heading and 'body' to --fvc-font-body, so text follows whatever typeface the application has put behind those tokens. Override --typography-font-family to retype this component alone, or the two global tokens to retype the whole design system.

Line Height

<Typography.Text size={16} lineHeight={24}>
  Readable body text
</Typography.Text>

Underline

<Typography.Text underline>Underlined text</Typography.Text>

Text Alignment

<Typography.Paragraph align="center">Centered paragraph</Typography.Paragraph>

Props

All sub-components share a common extended prop set on top of their respective Ant Design base props.

Shared Extended Props

| Prop | Type | Default | Description | | ------------ | -------------------------------------------------------------------------------- | ------- | ----------------------------------------------- | | align | CSSProperties['textAlign'] | — | Text alignment. | | color | string | — | Arbitrary CSS color value. | | font | 'body' \| 'heading' | 'body' | Typographic role. 'body' reads --fvc-font-body, 'heading' reads --fvc-font-heading. | | lineHeight | 'base' \| '12' \| '13' \| '14' \| '16' \| '20' \| '24' \| '32' \| number | — | Line height in px or a named token. | | size | '12' \| '13' \| '14' \| '16' \| '20' \| '24' \| '32' \| number | — | Font size in px. | | underline | boolean | false | Adds underline text decoration. | | weight | '200' \| '300' \| '400' \| '500' \| '600' \| '700' \| '800' \| '900' \| number | — | Font weight. | | testId | string | — | Maps to data-test-id on the rendered element. |

Each of these is applied as a per-element CSS custom property rather than a hardcoded style — see Per-element inline tokens.

Inherited Ant Design props

Everything not listed above is forwarded to the underlying Ant Design component untouched, including className, style, disabled, ellipsis, copyable, and onClick. Two props are handled specially:

  • prefixCls is set by this package to fvc-typography and cannot be overridden. Every Ant Design-generated class name is therefore fvc-prefixed (fvc-typography-disabled, fvc-typography-edit-content, …) rather than ant-prefixed. This is what lets the design-system stylesheet target them.
  • editable is intercepted and re-implemented — see Editable Mode.

<Typography.Title>

Extends Ant Design TitleProps plus the shared props above.

<Typography.Text>

Extends Ant Design TextProps plus the shared props above. Accepts an editable prop — see Editable Mode.

<Typography.Paragraph>

Extends Ant Design ParagraphProps plus the shared props above. Accepts an editable prop — see Editable Mode.

<Typography.Link>

Extends Ant Design LinkProps plus the shared props above, with size widened to accept 'small' | 'medium' — see Link.

Known limitation: the type prop

GenericTypographyProps declares a design-system type prop ('black' | 'white' | 'blue' | 'gray' | …), but it is not usable. It intersects with Ant Design's own type prop ('secondary' | 'success' | 'warning' | 'danger'), and because the two unions do not overlap the resulting type is undefined — every value is rejected, including Ant Design's own:

<Typography.Text type="blue">x</Typography.Text>      // ✗ TS2322
<Typography.Text type="secondary">x</Typography.Text> // ✗ TS2322

Use color for a one-off color, or a CSS token override for a global one. The design-system semantics behind type are still being decided (see the @TODO in types/Typography.types.ts).

TypeScript

The package ships with type definitions. No @types/ install needed.

import type {
  TypographyProps,
  TextProps,
  TitleProps,
  ParagraphProps,
  LinkProps,
  EditableConfig,
  BaseSizeType,
  BaseLineHeightType,
  LinkSizeType,
  TriggerType,
} from '@fvc/typography/types';

Consumer Example

import { Typography } from '@fvc/typography';

export function ArticlePreview({ title, summary, authorUrl, authorName }) {
  return (
    <article>
      <Typography.Title size={24} weight="700">
        {title}
      </Typography.Title>
      <Typography.Paragraph color="gray" size={14} lineHeight={20}>
        {summary}
      </Typography.Paragraph>
      <Typography.Link href={authorUrl} font="heading" weight="500">
        {authorName}
      </Typography.Link>
    </article>
  );
}

Storybook

Every scenario below has a runnable story with a copy-pasteable snippet under Show code:

bun run storybook

Then open Components → Typography. The editable stories are one scenario each — Basic, Controlled Editing, Controlled Value, Formatted Children, Trigger Types, Max Length, Auto Size, Disabled, Callbacks — plus a Playground with every control wired up.

Testing

Use testId to get a stable hook into the rendered element:

<Typography.Text testId="username-label">John Doe</Typography.Text>
screen.getByTestId('username-label');

testId maps to data-test-id (hyphenated), not data-testid. Testing Library's getByTestId matches data-testid by default, so either configure testIdAttribute: 'data-test-id' or query with getByRole / getByText instead.

Testing editable mode

The action bar controls are <button> elements with stable accessible names, so query them by role:

fireEvent.click(screen.getByRole('button', { name: 'Edit' }));
const editor = await screen.findByRole('textbox');

fireEvent.change(editor, { target: { value: 'New value' } });
fireEvent.keyDown(editor, { key: 'Enter', keyCode: 13 });
fireEvent.keyUp(editor, { key: 'Enter', keyCode: 13 });

expect(onChange).toHaveBeenLastCalledWith('New value');

| Control | Accessible name | | ------- | --------------- | | Pencil | Edit | | Confirm | Confirm | | Cancel | Cancel | | Clear | Clear |

In a DOM emulation such as happy-dom or jsdom, pressing Enter or Space on a real <button> does not synthesize a click — that is browser behavior these environments do not implement. Assert keyboard activation with fireEvent.click, and cover Enter/Escape on the editor itself as shown above.


Editable Mode

Typography.Text and Typography.Paragraph support inline editing. The component renders a custom action bar (pencil trigger, confirm, cancel, clear) that replaces the Ant Design default controls so the visual output matches the design system exactly.

Design reference: Figma — Editable text

The component never owns the value. It reports edits through onChange; you hold the text in your own state and render it as the children. editing is the only piece of state it will manage for you, and only if you leave it uncontrolled.

States

| State | Description | | ------------ | -------------------------------------------------------------------------------------------------- | | Display | Text is displayed with a pencil icon (✏) on the right. Clicking the pencil enters edit mode. | | Edit | An inline <textarea> replaces the text. Confirm (✓), cancel (⊘), and clear (🗑) controls appear. | | Disabled | Text is displayed in the secondary color. No affordance is rendered at all. |

State machine

Display ──(click ✏, or click text)──▶ Edit        onStart
Edit    ──(Enter)───────────────────▶ Display     onChange(newValue), onEnd
Edit    ──(click ✓)─────────────────▶ Display     onEnd
Edit    ──(Escape, or click ⊘)──────▶ Display     onChange(preEditValue), onCancel
Edit    ──(click 🗑)────────────────▶ Display     onChange('')

Read that table carefully before wiring a save handler — the exit paths do not all report a value, and two of them report a value you did not type. See Callbacks.

Uncontrolled (component owns editing state)

const [value, setValue] = useState('Click the pencil to edit.');

<Typography.Text weight="500" editable={{ onChange: setValue }}>
  {value}
</Typography.Text>;

editable also accepts the boolean shorthand when you need the affordance but no callbacks — useful for prototypes:

<Typography.Text editable>
  Editable, but nothing observes the result
</Typography.Text>

Controlled (consumer owns editing state)

const [editing, setEditing] = useState(false);
const [value, setValue] = useState('Hello world');

<Typography.Text
  weight="500"
  editable={{
    editing,
    onChange: setValue,
    onStart: () => setEditing(true),
    onEnd: () => setEditing(false),
    onCancel: () => setEditing(false),
  }}
>
  {value}
</Typography.Text>;

Passing editing switches off the internal state entirely: the component reports intent through onStart / onEnd / onCancel and you decide whether to honour it. Use this when a sibling needs to observe or drive edit mode — a toolbar "Edit" button, a "cancel all" action, an unsaved-changes guard.

Controlling editing is independent of controlling the value; combine both if you need to.

Callbacks

| Callback | Signature | Fires when | | ---------- | ------------------------- | --------------------------------------------------------------------------------------------------------- | | onStart | () => void | Edit mode is activated (pencil click, or text click when enabled). | | onChange | (value: string) => void | The value settles — on Enter with the new text, on cancel with the pre-edit text, on clear with ''. | | onEnd | () => void | The editor closes via Enter or the ✓ control. | | onCancel | () => void | The editor closes via Escape or the ⊘ control. |

Two things about onChange are easy to get wrong:

  • It is not a keystroke handler. It fires once per exit, not per character. Do not use it to drive a live character counter.
  • It fires on cancel too, replaying the value captured when editing started, so your state lands back where it began. Read onChange as "the value is now this", and onEnd as "the user committed" — a save request belongs in onEnd, not onChange.

Reverting on cancel

The pre-edit value is snapshotted when editing starts, and cancel replays that snapshot through onChange — so the revert survives your state changing while the editor is open. With string children the snapshot is the children; otherwise it is text (see Formatted children). If neither is available the snapshot is '', and cancelling clears the value.

Clearing

The 🗑 control emits onChange('') and closes the editor. It does not fire onCancel or onEnd — it is a value change, not a cancellation, so an unsaved-changes guard hooked to onCancel will not see it.

<Typography.Text
  editable={{
    onChange: (next) => setValue(next), // next === '' when cleared
  }}
>
  {value}
</Typography.Text>

autoSize

The inline editor is a <textarea> in both Text and Paragraph. autoSize shapes its height:

| Value | Behavior | | ---------------------- | --------------------------------------- | | omitted / true | Grows to fit the content, unbounded. | | { minRows, maxRows } | Grows between the bounds, then scrolls. | | false | Fixed at a single row, no growth. |

const [value, setValue] = useState('Multi-line content here.');

<Typography.Paragraph
  weight="500"
  editable={{
    autoSize: { minRows: 2, maxRows: 6 },
    onChange: setValue,
  }}
>
  {value}
</Typography.Paragraph>;

minRows also reserves height before anything is typed, which stops a multi-line paragraph from collapsing to one line the moment the editor opens. This matters most on Paragraph.

Max length

<Typography.Text
  editable={{
    maxLength: 80,
    onChange: setValue,
  }}
>
  {value}
</Typography.Text>

Enforced by the underlying <textarea>, so the cap applies while typing rather than being validated on commit — there is no error state to render.

Trigger type

By default (triggerType: ['icon']) the design-system pencil is the only trigger. Add 'text' to also enter edit mode when the text itself is clicked, or pass ['text'] alone to drop the pencil and make the text the sole trigger:

// pencil + click-to-edit
<Typography.Text editable={{ triggerType: ['icon', 'text'], onChange: setValue }}>
  {value}
</Typography.Text>

// text click only, no pencil
<Typography.Text editable={{ triggerType: ['text'], onChange: setValue }}>
  {value}
</Typography.Text>

The 'icon' trigger always renders the design-system pencil, never Ant Design's built-in edit icon.

Prefer ['text'] in dense layouts where a pencil on every row would be visual noise — but remember it removes the only visible affordance, so pair it with a hover or focus cue of your own.

Formatted (non-string) children

When the children are a plain string the inline editor seeds itself from them automatically. For formatted / non-string children there is no string to read, so pass text — it feeds both the editor's initial value and the revert-on-cancel snapshot:

const [value, setValue] = useState('Project Aurora');

<Typography.Text editable={{ text: value, onChange: setValue }}>
  <b>{value}</b>
</Typography.Text>;

Omit text with non-string children and the editor opens empty, and cancelling reverts to '' rather than the original text.

Disabled

disabled is a prop on the component, not a field of editable. It removes the affordance entirely — no pencil is rendered, so there is nothing for a pointer, keyboard, or screen reader to reach — and the text falls back to the disabled color:

<Typography.Text weight="500" editable disabled>
  Read-only content
</Typography.Text>

Prefer this over conditionally dropping the editable prop: the disabled color is part of the design system.

EditableConfig reference

Typography.Text and Typography.Paragraph expose the same editable surface — every field below applies to both.

| Field | Type | Default | Description | | ------------- | --------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------- | | text | string | — | Seed value for the inline editor and the cancel snapshot. Only needed when the children are not a plain string. | | editing | boolean | — | Controlled editing state. You must flip it in onEnd / onCancel. Omit for uncontrolled. | | onStart | () => void | — | Edit mode activated. | | onChange | (value: string) => void | — | The value settled. Fires on commit, cancel, and clear — not per keystroke. | | onEnd | () => void | — | Editor closed via Enter or ✓. | | onCancel | () => void | — | Editor closed via Escape or ⊘. | | maxLength | number | — | Maximum character count enforced in the editor. | | triggerType | ('icon' \| 'text')[] | ['icon'] | Surfaces that activate edit mode. 'icon' renders the design-system pencil; 'text' makes the text click-to-edit. | | autoSize | boolean \| { minRows?: number; maxRows?: number } | — | Editor height behavior. See autoSize. |

Passing editable as true is equivalent to passing {}.

TypeScript imports

import type { EditableConfig, TriggerType } from '@fvc/typography/types';

Accessibility

  • The pencil and the three action controls are real <button type="button"> elements. They are reachable with Tab, activate with Enter or Space, and expose accessible names Edit, Confirm, Cancel, and Clear.
  • type="button" is explicit so the controls never submit a surrounding <form>.
  • The inline editor receives focus automatically when editing starts.
  • Inside the editor, Enter commits and Escape cancels — consistent with ARIA editing patterns.
  • The editor's focus ring is styled via --typography-editable-border-focus and --typography-editable-focus-shadow.
  • With triggerType: ['text'] no button is rendered, so the only trigger is a click on the text. That surface is not keyboard-reachable — keep 'icon' in the list wherever keyboard access matters.

Keyboard reference

| Context | Key | Result | | --------------- | ----------------- | ----------------------------------------- | | Display mode | Tab | Focus the pencil. | | Pencil focused | Enter / Space | Open the editor. | | Editor focused | Enter | Commit — onChange(value), onEnd. | | Editor focused | Escape | Cancel — onChange(preEdit), onCancel. | | Edit mode | Tab | Move through ✓, ⊘, 🗑. | | Control focused | Enter / Space | Activate that control. |

Class names

The editable markup is a flex row wrapping the text node and the action bar. Target these for layout overrides:

| Class | Element | | ------------------------------------------ | ------------------------------------------------- | | fvc-typography-editable-row | Flex row containing content and action bar. | | fvc-typography-editable-content | Wrapper around the text / editor. | | fvc-typography-editable-content--editing | Added to the wrapper while editing. | | fvc-typography-editable-trigger | Pencil button. | | fvc-typography-editable-actions | Container for the three edit-mode buttons. | | fvc-typography-editable-action-confirm | ✓ button. | | fvc-typography-editable-action-cancel | ⊘ button. | | fvc-typography-editable-action-clear | 🗑 button. | | fvc-typography-edit-content | Ant Design's editor wrapper (holds the textarea). |

CSS tokens for editable mode

Override these in your stylesheet to reskin the inline editor without forking the component. All tokens are declared on :root in styles/variables.scss.

Input field

| Token | Default | Applies to | | ------------------------------------- | ---------------------------------- | ---------------------------------------- | | --typography-editable-border | 1px solid var(--gray-400) | Input border (default) | | --typography-editable-border-focus | 1px solid var(--blue-500) | Input border when focused | | --typography-editable-border-radius | 4px | Input corner radius | | --typography-editable-padding | 8px 12px | Input inner spacing | | --typography-editable-focus-shadow | 0 0 0 2px rgba(57, 92, 166, 0.1) | Focus ring shadow | | --typography-editable-transition | 0.2s ease | Border-color and box-shadow transition | | --typography-editable-input-ff | var(--fvc-font-body, sans-serif) | Input font family | | --typography-editable-input-fz | 14px | Input font size | | --typography-editable-input-fw | 400 | Input font weight | | --typography-editable-input-lh | 16px | Input line height | | --typography-editable-input-ls | 0 | Input letter spacing | | --typography-editable-input-color | var(--black-1000) | Input text color | | --typography-editable-display-fw | 500 | Display text weight (display / disabled) |

Row spacing

| Token | Default | Applies to | | ----------------------------------------------- | ---------- | ------------------------------- | | --typography-editable-content-padding | 10px 8px | Content wrapper in display mode | | --typography-editable-content-padding-editing | 4px 8px | Content wrapper while editing |

Action bar

| Token | Default | Applies to | | -------------------------------------------------- | ------------------ | -------------------------------- | | --typography-editable-action-trigger-color | var(--blue-500) | Pencil icon color | | --typography-editable-action-trigger-color-hover | var(--blue-700) | Pencil icon color on hover | | --typography-editable-action-confirm-color | var(--green-600) | Confirm (✓) icon color | | --typography-editable-action-confirm-color-hover | var(--green-700) | Confirm (✓) color on hover | | --typography-editable-action-cancel-color | var(--blue-500) | Cancel (⊘) icon color | | --typography-editable-action-cancel-color-hover | var(--blue-700) | Cancel (⊘) color on hover | | --typography-editable-action-clear-color | var(--red-800) | Clear (🗑) icon color | | --typography-editable-action-clear-color-hover | var(--red-800) | Clear (🗑) color on hover | | --typography-editable-action-btn-padding | 9px | Icon button padding (all sides) | | --typography-editable-action-btn-radius | 20px | Confirm / cancel / clear radius | | --typography-editable-action-clear-gap | 40px | Left gap before the clear button |

The icon glyphs themselves are a fixed 22px, set in constants.ts rather than by a token. --typography-editable-action-btn-size is declared in variables.scss but nothing consumes it — overriding it has no effect.

Disabled state

| Token | Default | Applies to | | -------------------------------------- | ---------------------- | -------------------------- | | --typography-editable-disabled-color | var(--blue-gray-600) | Text color when disabled |


Customisation

Set CSS custom properties in your own stylesheet to restyle @fvc/typography without forking the component or shipping a theme override file. No rebuild needed — the variables are read at runtime.

The component uses a two-tier CSS custom property system: global scale tokens that set the defaults for every instance, and per-element tokens that the component injects when you pass the matching prop.

Typography scale tokens (global defaults)

Override these in :root to restyle all instances without touching props. Defaults ship in styles/variables.scss.

| Token | Default | Applies to | | -------------------------- | ---------------------- | ---------------------------- | | --typography-font-family | var(--fvc-font-body, sans-serif) | Font family for all elements | | --typography-text-color | var(--black-1000) | Default text color | | --typography-text-fz | 16px | Text / Paragraph font size | | --typography-text-fw | 400 | Text / Paragraph font weight | | --typography-text-lh | 20px | Text / Paragraph line height | | --typography-h1-fz | 32px | H1 font size | | --typography-h1-fw | 700 | H1 font weight | | --typography-h1-lh | 32px | H1 line height | | --typography-h2-fz | 24px | H2 font size | | --typography-h2-fw | 700 | H2 font weight | | --typography-h2-lh | 32px | H2 line height | | --typography-h3-fz | 20px | H3 font size | | --typography-h3-fw | 700 | H3 font weight | | --typography-h3-lh | 20px | H3 line height | | --typography-h4-fz | 13px | H4 font size | | --typography-h4-fw | 700 | H4 font weight | | --typography-h4-lh | 17px | H4 line height | | --typography-h5-fz | 14px | H5 font size | | --typography-h5-fw | 700 | H5 font weight | | --typography-h5-lh | 17px | H5 line height |

Link tokens

Link has its own scale, independent of --typography-text-*.

| Token | Default | Applies to | | ---------------------------------- | ----------------- | ------------------------------- | | --typography-link-color | var(--blue-500) | Link color | | --typography-link-hover-color | var(--blue-700) | Link color on hover | | --typography-link-disabled-color | var(--gray-400) | Link color when disabled | | --typography-link-fw | 700 | Link font weight | | --typography-link-sm-fz | 14px | Font size for size="small" | | --typography-link-sm-lh | 16px | Line height for size="small" | | --typography-link-md-fz | 16px | Font size for size="medium" | | --typography-link-md-lh | 20px | Line height for size="medium" |

Per-element inline tokens

Injected as inline styles by the component when the corresponding prop is set. You normally do not set these manually — they exist so a prop on one instance never leaks into a global rule.

| Token | Set by prop | | -------------- | ------------ | | --text-color | color | | --text-fz | size | | --text-fw | weight | | --text-lh | lineHeight | | --text-ff | font | | --text-align | align |

Because these are read with a fallback to the scale token (var(--text-fz, var(--typography-text-fz))), a prop always wins over the global default for that one element, and omitting the prop falls back cleanly.

Override example

:root {
  --typography-font-family: 'Inter', sans-serif;
  --typography-text-color: #1a1a1a;
  --typography-link-color: #2563eb;
  --typography-link-hover-color: #1d4ed8;
  --typography-h1-fz: 40px;
  --typography-h1-lh: 48px;
  --typography-text-fz: 15px;
  --typography-text-lh: 24px;

  /* editable action bar */
  --typography-editable-action-confirm-color: #16a34a;
  --typography-editable-action-clear-gap: 24px;
}

Development

bun run lint
bun run type-check
bun run test
bun run build

Run the whole monorepo's Storybook from the repository root with bun run storybook.