/ component

Font Editor

Edit the CSS font shorthand β€” the most order-sensitive of the common shorthands β€” with a compile-time STRICT-ORDER PARSE. Order-free prefix tokens (style, variant, weight, stretch β€” each at most once), then a mandatory size, an optional / line-height, then a mandatory comma-separated family list. Or a system-font keyword as the whole value. 16px β†’ never (no family); italic oblique 16px serif β†’ never (two styles). The strict tier resolves any order or structure violation to never.

/ basic-usage

Build a font shorthand

Controlled value + onChange. Set the optional prefix (style/variant/weight/stretch), the mandatory size and family, and the optional line-height β€” the editor emits a valid, canonically-ordered font string.

italic 600 18px/1.4 'Inter', sans-serif
/ live-preview

See the shorthand on real text

Every control β€” style, variant, weight, stretch, size, line-height, and the family list (web-safe + generic) β€” composes the typed font string. The preview below renders sample text with exactly that shorthand applied.

Style
Variant
Weight
Stretch
Size
Line height
Font family
font: italic small-caps 500 18px/1.6 Georgia, serif;
preview
The quick brown fox jumps over the lazy dog
preview
The quick brown fox jumps over the lazy dog
preview
Pack my box with five dozen liquor jugs β€” 0123456789
font: italic small-caps 500 18px/1.6 Georgia, serif;
/ types

Three usage tiers

From useState-and-go to a full ordered-grammar parse at compile time.

01 casual
string

Pass any string

useState<string>. No compile-time validation; the runtime parser handles whatever you type β€” including var() family tokens.

16px var(--brand-stack), serif
const [value, setValue] = useState<string>("16px var(--brand-stack), serif")
02 intellisense
FontString

Suggestion-shaped hints

State typed as FontString β€” the system-font keywords plus any space-containing shorthand. It is also the onChange return type. Precise validation lives in the strict tier.

bold 16px sans-serif
const [value, setValue] = useState<FontString>("bold 16px sans-serif")
03 strict
FontLiteral<S>

The ordered grammar at compile time

cssFont() parses the prefix, size, optional line-height and family in order and resolves a missing size, missing family, or duplicate prefix kind to never β€” a type error before you run the code.

italic bold 16px/1.5 'Inter', sans-serif
cssFont("italic bold 16px/1.5 serif") // βœ“
// @ts-expect-error no font-family
cssFont("16px")
// @ts-expect-error two style tokens
cssFont("italic oblique 16px serif")

Note: var() / calc() are undecidable at compile time β€” use the casual or IntelliSense tier for those.

/ api

API

Public surface β€” component props, runtime helpers, and the type exports.

Β§ FontEditor / FontEditorPanel

<FontEditor
  value: FontString | (string & {})
  onChange: (next: FontString) => void
  className?: string
  aria-label?: string
/>

FontEditor is popover-wrapped; FontEditorPanel renders the same editor inline. Both are controlled.

PropTypeDescription
valueFontString | (string & {})Current CSS font value. Required. A shorthand or a system-font keyword.
onChange(next: FontString) => voidFires when the edited font changes. Emits the canonically-ordered string (or the system keyword).

Β§ Sub-components

<FontPreview value sampleText? editable? />

The live text preview β€” renders sample text with the built `font` shorthand applied inline. Editable sample text by default; pass sampleText for a fixed string.

<FamilyEditor value onChange />

The comma-separated family-list editor: edit/remove tokens and add from a web-safe + generic family list. Emits string[].

<PropertyField label>{control}</PropertyField>

A labelled control row used by the panel; exported for composition.

Β§ Runtime helpers

cssFont<S extends string>(value: S & FontLiteral<S>): S

Call-site validator. Mirrors cssTransform() / color() / easing().

parseFont(src: string): FontParts | null

String β†’ typed parts, or null on missing size / missing family / duplicate prefix kind / junk. A system keyword β†’ { kind: 'system' }. Tolerates var() family tokens.

formatFont(parts: FontParts): string

Canonical re-serialization: style variant weight stretch size[/lh] family. System keyword β†’ itself.

fontFamilies(src: string): string[]

Runtime mirror of FamiliesOf β€” the comma-separated family tokens in order.

defaultParts(): FontParts

Seed a fresh editor: { kind: 'shorthand', size: '16px', family: ['sans-serif'] }.

classifyStyle / classifyWeight / classifySize / classifyFamilyToken / …

Per-slot token classifiers (runtime mirror of the type predicates) plus the option-list constants that drive the UI.

Β§ Types

FontLiteral<S>
Strict validator β€” S if the ordered grammar holds (prefix ≀1 each β†’ size β†’ /lh? β†’ family), else never. System keywords pass.
FontString
Suggestion union: system keywords + any space-containing shorthand. Also the onChange return type.
FontStringMap / FontStringKey
Representative output-string shapes and the key union.
SystemFontKeyword / FontGenericFamily
The system-font keywords and the generic font-family keywords.
IsSystemFont<S>
true when S is a system-font keyword.
FamiliesOf<S>
Tuple of the comma-separated family tokens.
SizeOf<S> / LineHeightOf<S>
The size token, and the line-height token (never when absent).
FontParts
Discriminated-union state (system | shorthand), exported for advanced use.

Β§ Strict-tier scope

  • Full ordered parse: an order-free prefix (style / variant / weight / stretch, each at most once) β†’ mandatory <size> β†’ optional / <line-height> (attached 16px/1.5 and every spaced form) β†’ mandatory comma-separated <font-family> list. System keywords (caption…status-bar) pass as a whole value.
  • Rejects (β†’ never): missing size, missing family, duplicate prefix kind, line-height without a size, empty / garbage.
  • Weak-validated (documented): bare <custom-ident> family names are accepted as ident-safe (letters/digits/-/_/spaces; quoted strings accept any content). The <number> weight range 1–1000 is not bound-checked.
  • A leading normal consumes the first free prefix kind β€” sound for acceptance (CSS treats prefix normal as a no-op). oblique <angle> is deferred (only bare oblique validates strictly).
  • var() / calc() resolve to never at the strict tier (undecidable). The runtime parser accepts them β€” use the casual / IntelliSense tier.
/ install

Drop it in

One command via the shadcn CLI.

$ pnpm dlx shadcn@latest add https://turtiesocks.github.io/ridiculous/r/font-editor.json