---
id: "packages/active-format/examples"
title: "Examples"
description: "Every working example of active-format on one page. Each one shows the file that renders it."
url: "/packages/active-format/examples"
locale: "en"
revision: "3eaf488024b69bf5"
---

# Examples

All examples below are **Client** examples: they run entirely in your browser, with fixed input
values, so every reader sees the same input. The output is computed by your browser's `Intl`, so a
different browser version can write a value slightly differently.

**Example: One presentation for every formatter**

Client: Runs entirely in your browser. Source: `examples/format/presentation-switcher.tsx`.

```tsx
// apps/docs/examples/format/presentation-switcher.tsx
'use client';

import {useId, useState} from 'react';

import {
  composePresentationContext,
  formatByteSize,
  formatCurrency,
  formatDateOnly,
  formatDateTime,
  formatFractionAsPercent,
  formatList,
  formatNumber,
  toIanaTimeZone,
  type DateNotationPreset,
  type NumberNotationPreset,
  type PresentationProfileConfig,
  type TimeNotationPreset,
} from '@workspace/active-format';
import {
  Field,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {
  NativeSelect,
  NativeSelectOption,
} from '@workspace/active-ui/components/primitives/native-select';
import {
  Table,
  TableBody,
  TableCell,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

// Fixed values, so every reader sees the same input.
const CALENDAR_DAY = '2026-03-09';
const INSTANT = '2026-03-09T20:18:05Z';
const LANGUAGES = ['en', 'nl', 'de', 'fr', 'es', 'ja', 'ar-EG'];
const CURRENCIES = ['EUR', 'USD', 'GBP', 'JPY', 'KWD'];
const TIME_ZONES = [
  'Europe/Amsterdam',
  'America/New_York',
  'Asia/Tokyo',
  'UTC',
];

type Choice<T> = T | 'language';

// What each notation choice shows. The value stays the preset name the
// profile takes; the label says what it does, short enough to read in full
// in a field on a narrow screen.
const CHOICE_LABELS: Record<string, string> = {
  language: 'From the language',
  DMY_NUMERIC: 'Day, month, year',
  MDY_NUMERIC: 'Month, day, year',
  ISO_NUMERIC: 'Year, month, day (ISO)',
  H24: '24-hour clock',
  H12: '12-hour clock',
  DOT_DECIMAL_COMMA_GROUP: 'Decimal point, comma groups',
  COMMA_DECIMAL_DOT_GROUP: 'Decimal comma, point groups',
  COMMA_DECIMAL_SPACE_GROUP: 'Decimal comma, space groups',
};

/** One presentation, composed from language, profile and zone, feeding every formatter. */
export default function PresentationSwitcher() {
  const [language, setLanguage] = useState('en');
  const [currency, setCurrency] = useState('EUR');
  const [timeZone, setTimeZone] = useState('Europe/Amsterdam');
  const [dateFormat, setDateFormat] =
    useState<Choice<DateNotationPreset>>('language');
  const [timeFormat, setTimeFormat] =
    useState<Choice<TimeNotationPreset>>('language');
  const [numberFormat, setNumberFormat] =
    useState<Choice<NumberNotationPreset>>('language');

  // A profile field that is absent means "not configured": the language's own
  // convention applies.
  const profile: PresentationProfileConfig = {
    ...(dateFormat === 'language' ? {} : {dateFormat}),
    ...(timeFormat === 'language' ? {} : {timeFormat}),
    ...(numberFormat === 'language' ? {} : {numberFormat}),
  };
  const presentation = composePresentationContext(
    language,
    profile,
    toIanaTimeZone(timeZone),
  );

  const rows: [string, string][] = [
    ['formatDateOnly', formatDateOnly(CALENDAR_DAY, 'long', presentation)],
    [
      'formatDateTime',
      formatDateTime(INSTANT, 'numeric-datetime', presentation),
    ],
    ['formatNumber', formatNumber(1234567.891, 'decimal-2', presentation)],
    [
      'formatCurrency',
      formatCurrency({amount: 1234.5, currency}, presentation),
    ],
    [
      'formatFractionAsPercent',
      formatFractionAsPercent(0.125, 'decimal-1', presentation),
    ],
    [
      'formatByteSize',
      formatByteSize(1_536_000, presentation, {system: 'decimal'}),
    ],
    ['formatList', formatList(['Amsterdam', 'Utrecht', 'Delft'], presentation)],
  ];

  // The stage centres its content, which sizes a plain root to its widest
  // child: `w-full min-w-0` keeps the demo to the width there is, so the
  // fields wrap and only the result table scrolls, in its own named region.
  return (
    <div className="flex w-full min-w-0 flex-col gap-4">
      {/* Columns follow the width the demo has, not the screen: beside the
          sidebar a three-column row at `sm` left each field too narrow for
          its longest choice. */}
      <div className="grid grid-cols-[repeat(auto-fill,minmax(min(100%,15rem),1fr))] gap-3">
        <Select
          label="Language"
          value={language}
          onChange={setLanguage}
          options={LANGUAGES}
        />
        <Select
          label="Currency"
          value={currency}
          onChange={setCurrency}
          options={CURRENCIES}
        />
        <Select
          label="Time zone"
          value={timeZone}
          onChange={setTimeZone}
          options={TIME_ZONES}
        />
        <Select
          label="Date notation"
          value={dateFormat}
          onChange={(value) =>
            setDateFormat(value as Choice<DateNotationPreset>)
          }
          options={['language', 'DMY_NUMERIC', 'MDY_NUMERIC', 'ISO_NUMERIC']}
        />
        <Select
          label="Clock"
          value={timeFormat}
          onChange={(value) =>
            setTimeFormat(value as Choice<TimeNotationPreset>)
          }
          options={['language', 'H24', 'H12']}
        />
        <Select
          label="Number notation"
          value={numberFormat}
          onChange={(value) =>
            setNumberFormat(value as Choice<NumberNotationPreset>)
          }
          options={[
            'language',
            'DOT_DECIMAL_COMMA_GROUP',
            'COMMA_DECIMAL_DOT_GROUP',
            'COMMA_DECIMAL_SPACE_GROUP',
          ]}
        />
      </div>
      <Table scrollLabel="Results, scrolls sideways">
        <TableBody>
          {rows.map(([name, text]) => (
            <TableRow key={name}>
              <TableCell className="text-muted-foreground font-mono text-xs">
                {name}
              </TableCell>
              <TableCell>
                <output suppressHydrationWarning>{text}</output>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function Select({
  label,
  value,
  onChange,
  options,
}: {
  label: string;
  value: string;
  onChange: (value: string) => void;
  options: readonly string[];
}) {
  const id = useId();
  return (
    <Field className="min-w-0">
      <FieldLabel htmlFor={id}>{label}</FieldLabel>
      <NativeSelect
        id={id}
        className="w-full"
        value={value}
        onChange={(event) => onChange(event.target.value)}
      >
        {options.map((option) => (
          <NativeSelectOption key={option} value={option}>
            {CHOICE_LABELS[option] ?? option}
          </NativeSelectOption>
        ))}
      </NativeSelect>
    </Field>
  );
}
```

**Example: Currency amounts**

Client: Runs entirely in your browser. Source: `examples/format/currency-amounts.tsx`.

```tsx
// apps/docs/examples/format/currency-amounts.tsx
'use client';

import {formatCurrency, toDecimalString} from '@workspace/active-format';
import {
  Table,
  TableBody,
  TableCell,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

const EXACT = '12345678901234567.89';

/**
 * The currency comes from the data, the notation from the presentation.
 * Decimals follow the currency's minor units unless the call overrides them.
 */
export default function CurrencyAmounts() {
  const rows: [string, string][] = [
    ['EUR, en', formatCurrency({amount: 1234.5, currency: 'EUR'}, 'en')],
    ['EUR, nl', formatCurrency({amount: 1234.5, currency: 'EUR'}, 'nl')],
    [
      'JPY (no minor unit)',
      formatCurrency({amount: 1234.5, currency: 'JPY'}, 'en'),
    ],
    [
      'KWD (three decimals)',
      formatCurrency({amount: 1234.5, currency: 'KWD'}, 'en'),
    ],
    [
      'Whole amounts',
      formatCurrency({amount: 1234.5, currency: 'EUR'}, 'en', {decimals: 0}),
    ],
    [
      'A change, signed',
      formatCurrency({amount: 12.5, currency: 'EUR'}, 'en', {
        sign: 'exceptZero',
      }),
    ],
    [
      'Exact decimal string',
      formatCurrency({amount: toDecimalString(EXACT), currency: 'EUR'}, 'en'),
    ],
    [
      'The same value as a number',
      formatCurrency({amount: Number(EXACT), currency: 'EUR'}, 'en'),
    ],
    [
      'No amount',
      formatCurrency({amount: null, currency: 'EUR'}, 'en') || '(empty string)',
    ],
  ];
  return (
    <Table>
      <TableBody>
        {rows.map(([label, text]) => (
          <TableRow key={label}>
            <TableCell className="text-muted-foreground">{label}</TableCell>
            <TableCell className="font-mono">
              <output suppressHydrationWarning>{text}</output>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}
```

**Example: Calendar day versus instant**

Client: Runs entirely in your browser. Source: `examples/format/calendar-day-vs-instant.tsx`.

```tsx
// apps/docs/examples/format/calendar-day-vs-instant.tsx
'use client';

import {
  calendarDayOf,
  composePresentationContext,
  formatDateOnly,
  formatDateTime,
  toIanaTimeZone,
} from '@workspace/active-format';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

// One moment, late in the evening in UTC.
const INSTANT = '2026-03-09T23:30:00Z';
// One calendar day: a birthday, a due date. It has no zone.
const CALENDAR_DAY = '2026-03-09';
const ZONES = ['UTC', 'Europe/Amsterdam', 'America/New_York', 'Asia/Tokyo'];

/**
 * An instant reads differently per zone; a calendar day never moves.
 */
export default function CalendarDayVsInstant() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Zone</TableHead>
          <TableHead>The instant</TableHead>
          <TableHead>Its day there</TableHead>
          <TableHead>The calendar day</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {ZONES.map((zone) => {
          const timeZone = toIanaTimeZone(zone);
          const presentation = composePresentationContext('en', null, timeZone);
          return (
            <TableRow key={zone}>
              <TableCell className="font-mono text-xs">{zone}</TableCell>
              <TableCell suppressHydrationWarning>
                {formatDateTime(INSTANT, 'datetime', presentation)}
              </TableCell>
              <TableCell className="font-mono text-xs">
                {calendarDayOf(INSTANT, timeZone)}
              </TableCell>
              <TableCell suppressHydrationWarning>
                {formatDateOnly(CALENDAR_DAY, 'long', presentation)}
              </TableCell>
            </TableRow>
          );
        })}
      </TableBody>
    </Table>
  );
}
```

**Example: Relative time**

Client: Runs entirely in your browser. Source: `examples/format/recency.tsx`.

```tsx
// apps/docs/examples/format/recency.tsx
'use client';

import {resolveRecency} from '@workspace/active-format';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

// A fixed "now": the example reads no clock, so every reader sees the same.
const NOW = new Date('2026-03-09T12:00:00Z');
const VALUES = [
  '2026-03-09T11:58:00Z',
  '2026-03-08T09:00:00Z',
  '2026-03-12T12:00:00Z',
  '2026-02-01T08:00:00Z',
  '2026-03-09',
];

/**
 * Relative within the threshold (seven days by default), absolute beyond it,
 * unavailable for a value that is not an instant.
 */
export default function Recency() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Value</TableHead>
          <TableHead>mode</TableHead>
          <TableHead>text</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {VALUES.map((value) => {
          const result = resolveRecency(value, 'en', {now: NOW});
          return (
            <TableRow key={value}>
              <TableCell className="font-mono text-xs">{value}</TableCell>
              <TableCell className="font-mono text-xs">{result.mode}</TableCell>
              <TableCell suppressHydrationWarning>
                {result.text || '(none)'}
              </TableCell>
            </TableRow>
          );
        })}
      </TableBody>
    </Table>
  );
}
```

**Example: Reading a typed number**

Client: Runs entirely in your browser. Source: `examples/format/number-input.tsx`.

```tsx
// apps/docs/examples/format/number-input.tsx
'use client';

import {useId, useState} from 'react';

import {
  classifyNumberInput,
  getNumberInputPlaceholder,
} from '@workspace/active-format';
import {
  DescriptionItem,
  DescriptionList,
} from '@workspace/active-ui/components/composed/description-list';
import {
  Field,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {Input} from '@workspace/active-ui/components/primitives/input';
import {
  NativeSelect,
  NativeSelectOption,
} from '@workspace/active-ui/components/primitives/native-select';

/**
 * Reading a typed number back with the same grammar that writes it. The
 * result says whether the text is empty, incomplete, invalid or valid, and
 * why a text is refused. Grouping separators are refused while typing and
 * accepted in pasted text.
 */
export default function NumberInput() {
  const inputId = useId();
  const languageId = useId();
  const modeId = useId();
  const [language, setLanguage] = useState('nl');
  const [mode, setMode] = useState<'typing' | 'paste'>('typing');
  const [text, setText] = useState('1234,5');
  const result = classifyNumberInput(text, language, {mode, maxDecimals: 2});
  return (
    <div className="flex flex-col gap-4">
      <div className="grid gap-3 sm:grid-cols-3">
        <Field>
          <FieldLabel htmlFor={languageId}>Language</FieldLabel>
          <NativeSelect
            id={languageId}
            value={language}
            onChange={(event) => setLanguage(event.target.value)}
          >
            {['nl', 'en', 'de', 'fr'].map((option) => (
              <NativeSelectOption key={option} value={option}>
                {option}
              </NativeSelectOption>
            ))}
          </NativeSelect>
        </Field>
        <Field>
          <FieldLabel htmlFor={modeId}>Input</FieldLabel>
          <NativeSelect
            id={modeId}
            value={mode}
            onChange={(event) =>
              setMode(event.target.value === 'paste' ? 'paste' : 'typing')
            }
          >
            <NativeSelectOption value="typing">Typed</NativeSelectOption>
            <NativeSelectOption value="paste">Pasted</NativeSelectOption>
          </NativeSelect>
        </Field>
        <Field>
          <FieldLabel htmlFor={inputId}>
            Amount (at most two decimals)
          </FieldLabel>
          <Input
            id={inputId}
            value={text}
            placeholder={getNumberInputPlaceholder(language, 2)}
            onChange={(event) => setText(event.target.value)}
            inputMode="decimal"
          />
        </Field>
      </div>
      <DescriptionList orientation="inline" columns={1}>
        <DescriptionItem label="status">
          <span className="font-mono" data-testid="number-status">
            {result.status}
          </span>
        </DescriptionItem>
        <DescriptionItem label="value">
          <span className="font-mono">{result.value || '(none)'}</span>
        </DescriptionItem>
        <DescriptionItem label="number">
          <span className="font-mono">{result.number ?? 'null'}</span>
        </DescriptionItem>
        <DescriptionItem label="rejection">
          <span className="font-mono">{result.rejection ?? '(none)'}</span>
        </DescriptionItem>
      </DescriptionList>
    </div>
  );
}
```

**Example: Reading a typed date**

Client: Runs entirely in your browser. Source: `examples/format/date-input.tsx`.

```tsx
// apps/docs/examples/format/date-input.tsx
'use client';

import {useId, useState} from 'react';

import {
  classifyDateInput,
  formatDateOnly,
  getDateInputPattern,
} from '@workspace/active-format';
import {
  DescriptionItem,
  DescriptionList,
} from '@workspace/active-ui/components/composed/description-list';
import {
  Field,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {Input} from '@workspace/active-ui/components/primitives/input';
import {
  NativeSelect,
  NativeSelectOption,
} from '@workspace/active-ui/components/primitives/native-select';

/**
 * A typed date in the reader's notation, read back as an ISO calendar day.
 * Impossible dates are invalid, never moved to the next valid day.
 */
export default function DateInput() {
  const inputId = useId();
  const languageId = useId();
  const [language, setLanguage] = useState('de');
  const [text, setText] = useState('29.02.2028');
  const pattern = getDateInputPattern(language);
  const result = classifyDateInput(text, language);
  return (
    <div className="flex flex-col gap-4">
      <div className="grid gap-3 sm:grid-cols-2">
        <Field>
          <FieldLabel htmlFor={languageId}>Language</FieldLabel>
          <NativeSelect
            id={languageId}
            value={language}
            onChange={(event) => setLanguage(event.target.value)}
          >
            {['de', 'nl', 'en', 'fr'].map((option) => (
              <NativeSelectOption key={option} value={option}>
                {option}
              </NativeSelectOption>
            ))}
          </NativeSelect>
        </Field>
        <Field>
          <FieldLabel htmlFor={inputId}>Date ({pattern})</FieldLabel>
          <Input
            id={inputId}
            value={text}
            placeholder={pattern}
            onChange={(event) => setText(event.target.value)}
          />
        </Field>
      </div>
      <DescriptionList orientation="inline" columns={1}>
        <DescriptionItem label="status">
          <span className="font-mono" data-testid="date-status">
            {result.status}
          </span>
        </DescriptionItem>
        <DescriptionItem label="value">
          <span className="font-mono">{result.value || '(none)'}</span>
        </DescriptionItem>
        <DescriptionItem label="read as">
          <span suppressHydrationWarning>
            {result.status === 'valid'
              ? formatDateOnly(result.value, 'full', language)
              : '(none)'}
          </span>
        </DescriptionItem>
      </DescriptionList>
    </div>
  );
}
```

**Example: Unsupported language tags**

Client: Runs entirely in your browser. Source: `examples/format/locale-errors.tsx`.

```tsx
// apps/docs/examples/format/locale-errors.tsx
'use client';

import {
  formatNumber,
  isSupportedLocale,
  UnsupportedLocaleError,
} from '@workspace/active-format';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

// Tags a host might receive from a profile, a header or a setting.
const TAGS = ['en-GB', 'nl', 'pt-BR', 'zz-ZZ', 'en_US', 'ja-JP-u-ca-japanese'];

function tryFormat(tag: string): string {
  try {
    return formatNumber(1234.5, 'decimal-1', tag);
  } catch (err) {
    if (err instanceof UnsupportedLocaleError) {
      return `UnsupportedLocaleError (${err.reason})`;
    }
    throw err;
  }
}

/**
 * A refused tag throws: there is no silent English or host default. A host
 * that wants to test a tag first asks `isSupportedLocale`.
 */
export default function LocaleErrors() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Tag</TableHead>
          <TableHead>isSupportedLocale</TableHead>
          <TableHead>formatNumber(1234.5)</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {TAGS.map((tag) => (
          <TableRow key={tag}>
            <TableCell className="font-mono text-xs">{tag}</TableCell>
            <TableCell className="font-mono text-xs" suppressHydrationWarning>
              {String(isSupportedLocale(tag))}
            </TableCell>
            <TableCell suppressHydrationWarning>{tryFormat(tag)}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}
```
