Documentation menu

Ponytail UI

A small semantic layer for native pages: shared tokens, four color palettes, light and dark modes, and browser-native components.

The open workbench

Ponytail UI makes Bunfork pages feel mechanical, grounded, and candid. Content stays readable, controls look operable, and green remains the default signal color. Tonal surfaces carry hierarchy; decoration does not.

This is a clean-room CSS layer informed by open design systems and Ponytail's native-platform discipline. It is not a Tailwind CSS fork, does not implement Tailwind utilities, and adds no frontend build step or runtime dependency.

Native means semantics and browser behavior, not browser appearance. Every visible control is reset and redrawn with theme tokens, including text fields, select arrows, checkboxes, radio buttons, switches, file and color inputs, ranges, progress, meters, disclosure markers, popovers, and scrollbars.

Load the foundation

The bundled base layout loads the existing site styles first and the reusable foundation second:

<link rel="stylesheet" href="/assets/app.css">
<link rel="stylesheet" href="/assets/ponytail.css">

ponytail.css owns semantic --pt-* tokens and reusable .pt-* classes. Existing Bunfork variables such as --bg and --accent remain compatibility aliases. New components should consume the --pt-* names directly.

The ten .ds-* snippets in .impeccable/design.json are isolated shadow-DOM previews for design tooling. They are intentionally self-contained and are not a second runtime component API.

Themes are token scopes

The default mode follows prefers-color-scheme through native light-dark(). Set data-mode="light" or data-mode="dark" on any container for a deterministic preview. Set one palette per page with data-palette.

The component catalog adds a no-script theme laboratory. Token-skinned radio controls and :has() switch the full page between Auto, Light, Dark, Fork, Tide, Ember, and Berry states.

Fork lightDefault green
Fork darkDefault green
Tide lightInformational blue
Tide darkInformational blue
Ember lightWarm action
Ember darkWarm action
Berry lightExpressive accent
Berry darkExpressive accent
<main data-palette="tide">...</main>
<aside data-mode="dark" data-palette="ember">...</aside>

Semantic tokens before component values

Core Ponytail UI token groups and their intended use
GroupExamplesUse
Canvas--pt-color-canvas, --pt-color-surfacePage and contained surfaces
Ink--pt-color-ink, --pt-color-ink-mutedPrimary and supporting text
Structure--pt-color-border, --pt-color-border-strongGrouping and control boundaries
Action--pt-color-accent, --pt-color-on-accentOne primary accent per scope
Controls--pt-color-control, --pt-color-control-hover, --pt-color-control-disabledComplete themed state cycle for native inputs
Meaning--pt-color-success, --pt-color-warning, --pt-color-dangerStatus paired with text
Rhythm--pt-space-1 through --pt-space-9A four-pixel-based spacing ladder
Shape--pt-radius-sm, --pt-radius-lgSix to twelve pixels; pills only for compact metadata
Motion--pt-duration-fast, --pt-ease-outState feedback, never decorative delay
Layers--pt-z-sticky through --pt-z-tooltipNamed stacking contexts

Type stays local and legible

The system font stack keeps Bunfork fast and familiar. A single family uses weight and scale for hierarchy. Monospace is reserved for code, commands, keys, and tabular numbers; it is not a costume for every technical label.

Display
clamp(2.75rem, 7vw, 5.5rem), maximum twelve characters per line where practical, letter spacing no tighter than -.04em.
Body
At least 1rem, line height near 1.65, and a target measure of 70ch.
Labels
Sentence case by default. Uppercase is reserved for genuine codes or protocol values.
Numbers
Use tabular figures for changing metrics, prices, and timers.

A few composition primitives

Use .pt-stack for vertical rhythm, .pt-cluster for wrapping action groups, .pt-grid for content-driven grids, and .pt-split only when two regions have a real relationship. All multi-column primitives collapse below the content breakpoint.

Bunfork's native-page CSP rejects inline presentation. Use themed attributes such as data-gap and data-width rather than a style attribute. Static framework artifacts have a separate CSP that permits the inline bootstrap code some exporters require.

<section class="pt-stack" data-gap="6">
  <h2>One clear message</h2>
  <p class="pt-measure">Supporting content stays readable.</p>
  <div class="pt-cluster">...</div>
</section>

Cards are not the default grouping tool. Start with spacing, headings, and a divider. Add .pt-panel or .pt-card only when a bounded object needs a visible edge.

Component contract

  • Start with the native element: button, input, details, progress, table, or popover.
  • Remove the user-agent visual skin, then rebuild every visible part from semantic tokens. No default checkbox, radio, select, range, file button, progress bar, details marker, dialog edge, or scrollbar ships inside Ponytail UI.
  • Optional user-agent chrome such as search cancel buttons and numeric steppers stays visually suppressed; native Escape and arrow-key behavior remains available.
  • Use native state attributes such as disabled, checked, open, required, aria-current, and aria-invalid.
  • Use data-variant, data-tone, or data-palette only for presentation that HTML does not already express.
  • Keep reusable components small and focused on one semantic responsibility; compose existing primitives instead of growing mode-heavy universal components.
  • Keep labels visible, error text adjacent, controls at least 44 CSS pixels high, and focus rings visible.
  • Do not claim menu, combobox, tab, dialog, or toast behavior from CSS alone. Use a simpler server link or native disclosure until keyboard behavior is implemented and tested.

Open all 20 component genres

Open all 110 assistant concepts

The assistant catalog uses four honest coverage states: native behavior, an existing primitive, an escaped/static fallback, or a deferred row with a named database, runtime, permission, or security prerequisite. It covers observable concepts, not React or SDK APIs.

Accessibility is part of the API

Contrast
Normal text reaches 4.5:1 and large text 3:1. Structural control boundaries target 3:1 when the boundary identifies the control.
Keyboard
DOM order matches visual order. The skip link transfers focus to the programmatically focusable main landmark. Every action uses a native interactive element and receives a visible focus ring.
Touch
Primary controls and navigation targets use a minimum 44-pixel block size with at least eight pixels between adjacent targets.
Motion
Transitions explain hover, press, open, and state changes. Reduced-motion removes pulse and compresses transition time.
Forced colors
Controls retain visible system-color borders and focus indication.
Language
Logical properties and flexible widths support text expansion, CJK, and right-to-left document direction.

Pages extending the base layout can override the document_language and document_direction blocks. Shared navigation, tables, form adornments, and decorative positioning use logical inline properties so rtl changes layout direction without a second stylesheet.

Research and provenance

The component taxonomy was checked against Tailwind CSS 4, Radix, shadcn/ui, Primer, Carbon, Material Web, Bootstrap, GOV.UK Frontend, USWDS, Fluent UI, Shopify Polaris, Pico CSS, and Ponytail. Bunfork copies no upstream component source or brand assets.

Implementation choices favor Pico's semantic-element posture, government-system form clarity, Primer and Carbon density, and Radix interaction anatomy. Assistant-facing concepts were independently mapped from pinned public metadata into one Bunfork vocabulary; no source, assets, runtime API, or visual trade dress was ported. The full source matrix, pinned commits, licenses, and deferred behaviors live in docs/ui-foundation-research.md and docs/assistant-component-coverage.md.