Menu
AkurAI-Build
publicLatest change 0cf28150faedd8c150474a4f16be4c9922c04f56 - feat: add Ponytail UI design system by Olafur Bui
{% extends "layouts/docs.html" %}
{% block title %}Ponytail UI design system · Bunfork docs{% endblock %}
{% block description %}Use Bunfork's CSS-first Ponytail UI tokens, light and dark color palettes, native components, accessibility rules, and component catalog.{% endblock %}
{% block breadcrumb %}Design system{% endblock %}
{% block docs_title %}Ponytail UI{% endblock %}
{% block docs_summary %}A small semantic layer for native pages: shared tokens, four color palettes, light and dark modes, and browser-native components.{% endblock %}
{% block docs_content %}
<section class="doc-section" aria-labelledby="design-principles">
<h2 id="design-principles">The open workbench</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<aside class="doc-note"><strong>Scene:</strong> a developer reads these pages at a bright desk during setup, then returns from a dim operations room during an incident. Both modes must retain the same hierarchy and brand.</aside>
</section>
<section class="doc-section" aria-labelledby="design-install">
<h2 id="design-install">Load the foundation</h2>
<p>The bundled base layout loads the existing site styles first and the reusable foundation second:</p>
<pre><code><link rel="stylesheet" href="/assets/app.css">
<link rel="stylesheet" href="/assets/ponytail.css"></code></pre>
<p><code>ponytail.css</code> owns semantic <code>--pt-*</code> tokens and reusable <code>.pt-*</code> classes. Existing Bunfork variables such as <code>--bg</code> and <code>--accent</code> remain compatibility aliases. New components should consume the <code>--pt-*</code> names directly.</p>
<p>The ten <code>.ds-*</code> snippets in <code>.impeccable/design.json</code> are isolated shadow-DOM previews for design tooling. They are intentionally self-contained and are not a second runtime component API.</p>
</section>
<section class="doc-section" aria-labelledby="design-themes">
<h2 id="design-themes">Themes are token scopes</h2>
<p>The default mode follows <code>prefers-color-scheme</code> through native <code>light-dark()</code>. Set <code>data-mode="light"</code> or <code>data-mode="dark"</code> on any container for a deterministic preview. Set one palette per page with <code>data-palette</code>.</p>
<p>The component catalog adds a no-script theme laboratory. Token-skinned radio controls and <code>:has()</code> switch the full page between Auto, Light, Dark, Fork, Tide, Ember, and Berry states.</p>
<div class="pt-theme-grid" aria-label="Four palettes in light and dark modes">
<div class="pt-theme-swatch" data-mode="light" data-palette="fork"><strong>Fork light</strong><small>Default green</small><i aria-hidden="true"></i></div>
<div class="pt-theme-swatch" data-mode="dark" data-palette="fork"><strong>Fork dark</strong><small>Default green</small><i aria-hidden="true"></i></div>
<div class="pt-theme-swatch" data-mode="light" data-palette="tide"><strong>Tide light</strong><small>Informational blue</small><i aria-hidden="true"></i></div>
<div class="pt-theme-swatch" data-mode="dark" data-palette="tide"><strong>Tide dark</strong><small>Informational blue</small><i aria-hidden="true"></i></div>
<div class="pt-theme-swatch" data-mode="light" data-palette="ember"><strong>Ember light</strong><small>Warm action</small><i aria-hidden="true"></i></div>
<div class="pt-theme-swatch" data-mode="dark" data-palette="ember"><strong>Ember dark</strong><small>Warm action</small><i aria-hidden="true"></i></div>
<div class="pt-theme-swatch" data-mode="light" data-palette="berry"><strong>Berry light</strong><small>Expressive accent</small><i aria-hidden="true"></i></div>
<div class="pt-theme-swatch" data-mode="dark" data-palette="berry"><strong>Berry dark</strong><small>Expressive accent</small><i aria-hidden="true"></i></div>
</div>
<pre><code><main data-palette="tide">...</main>
<aside data-mode="dark" data-palette="ember">...</aside></code></pre>
<aside class="doc-note"><strong>Theme lock:</strong> choose one palette and one mode strategy for a real page. Nested scopes exist for documentation and embedded products, not random section-by-section color changes.</aside>
</section>
<section class="doc-section" aria-labelledby="design-tokens">
<h2 id="design-tokens">Semantic tokens before component values</h2>
<div class="table-wrap">
<table class="pt-token-table">
<caption class="pt-visually-hidden">Core Ponytail UI token groups and their intended use</caption>
<thead><tr><th scope="col">Group</th><th scope="col">Examples</th><th scope="col">Use</th></tr></thead>
<tbody>
<tr><td>Canvas</td><td><code>--pt-color-canvas</code>, <code>--pt-color-surface</code></td><td>Page and contained surfaces</td></tr>
<tr><td>Ink</td><td><code>--pt-color-ink</code>, <code>--pt-color-ink-muted</code></td><td>Primary and supporting text</td></tr>
<tr><td>Structure</td><td><code>--pt-color-border</code>, <code>--pt-color-border-strong</code></td><td>Grouping and control boundaries</td></tr>
<tr><td>Action</td><td><code>--pt-color-accent</code>, <code>--pt-color-on-accent</code></td><td>One primary accent per scope</td></tr>
<tr><td>Controls</td><td><code>--pt-color-control</code>, <code>--pt-color-control-hover</code>, <code>--pt-color-control-disabled</code></td><td>Complete themed state cycle for native inputs</td></tr>
<tr><td>Meaning</td><td><code>--pt-color-success</code>, <code>--pt-color-warning</code>, <code>--pt-color-danger</code></td><td>Status paired with text</td></tr>
<tr><td>Rhythm</td><td><code>--pt-space-1</code> through <code>--pt-space-9</code></td><td>A four-pixel-based spacing ladder</td></tr>
<tr><td>Shape</td><td><code>--pt-radius-sm</code>, <code>--pt-radius-lg</code></td><td>Six to twelve pixels; pills only for compact metadata</td></tr>
<tr><td>Motion</td><td><code>--pt-duration-fast</code>, <code>--pt-ease-out</code></td><td>State feedback, never decorative delay</td></tr>
<tr><td>Layers</td><td><code>--pt-z-sticky</code> through <code>--pt-z-tooltip</code></td><td>Named stacking contexts</td></tr>
</tbody>
</table>
</div>
</section>
<section class="doc-section" aria-labelledby="design-type">
<h2 id="design-type">Type stays local and legible</h2>
<p>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.</p>
<dl class="reference-list">
<div><dt>Display</dt><dd><code>clamp(2.75rem, 7vw, 5.5rem)</code>, maximum twelve characters per line where practical, letter spacing no tighter than <code>-.04em</code>.</dd></div>
<div><dt>Body</dt><dd>At least <code>1rem</code>, line height near <code>1.65</code>, and a target measure of <code>70ch</code>.</dd></div>
<div><dt>Labels</dt><dd>Sentence case by default. Uppercase is reserved for genuine codes or protocol values.</dd></div>
<div><dt>Numbers</dt><dd>Use tabular figures for changing metrics, prices, and timers.</dd></div>
</dl>
</section>
<section class="doc-section" aria-labelledby="design-layout">
<h2 id="design-layout">A few composition primitives</h2>
<p>Use <code>.pt-stack</code> for vertical rhythm, <code>.pt-cluster</code> for wrapping action groups, <code>.pt-grid</code> for content-driven grids, and <code>.pt-split</code> only when two regions have a real relationship. All multi-column primitives collapse below the content breakpoint.</p>
<p>Bunfork's native-page CSP rejects inline presentation. Use themed attributes such as <code>data-gap</code> and <code>data-width</code> rather than a <code>style</code> attribute. Static framework artifacts have a separate CSP that permits the inline bootstrap code some exporters require.</p>
<pre><code><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></code></pre>
<p>Cards are not the default grouping tool. Start with spacing, headings, and a divider. Add <code>.pt-panel</code> or <code>.pt-card</code> only when a bounded object needs a visible edge.</p>
</section>
<section class="doc-section" aria-labelledby="design-components">
<h2 id="design-components">Component contract</h2>
<ul class="check-list">
<li>Start with the native element: <code>button</code>, <code>input</code>, <code>details</code>, <code>progress</code>, <code>table</code>, or <code>popover</code>.</li>
<li>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.</li>
<li>Optional user-agent chrome such as search cancel buttons and numeric steppers stays visually suppressed; native Escape and arrow-key behavior remains available.</li>
<li>Use native state attributes such as <code>disabled</code>, <code>checked</code>, <code>open</code>, <code>required</code>, <code>aria-current</code>, and <code>aria-invalid</code>.</li>
<li>Use <code>data-variant</code>, <code>data-tone</code>, or <code>data-palette</code> only for presentation that HTML does not already express.</li>
<li>Keep labels visible, error text adjacent, controls at least 44 CSS pixels high, and focus rings visible.</li>
<li>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.</li>
</ul>
<p><a class="section-link" href="/examples/components">Open all 20 component genres <span aria-hidden="true">→</span></a></p>
</section>
<section class="doc-section" aria-labelledby="design-accessibility">
<h2 id="design-accessibility">Accessibility is part of the API</h2>
<dl class="reference-list">
<div><dt>Contrast</dt><dd>Normal text reaches 4.5:1 and large text 3:1. Structural control boundaries target 3:1 when the boundary identifies the control.</dd></div>
<div><dt>Keyboard</dt><dd>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.</dd></div>
<div><dt>Touch</dt><dd>Primary controls and navigation targets use a minimum 44-pixel block size with at least eight pixels between adjacent targets.</dd></div>
<div><dt>Motion</dt><dd>Transitions explain hover, press, open, and state changes. Reduced-motion removes pulse and compresses transition time.</dd></div>
<div><dt>Forced colors</dt><dd>Controls retain visible system-color borders and focus indication.</dd></div>
<div><dt>Language</dt><dd>Logical properties and flexible widths support text expansion, CJK, and right-to-left document direction.</dd></div>
</dl>
<p>Pages extending the base layout can override the <code>document_language</code> and <code>document_direction</code> blocks. Shared navigation, tables, form adornments, and decorative positioning use logical inline properties so <code>rtl</code> changes layout direction without a second stylesheet.</p>
</section>
<section class="doc-section" aria-labelledby="design-research">
<h2 id="design-research">Research and provenance</h2>
<p>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.</p>
<p>Implementation choices favor Pico's semantic-element posture, government-system form clarity, Primer and Carbon density, and Radix interaction anatomy. The full source matrix, pinned commits, licenses, and deferred JavaScript behaviors live in <code>docs/ui-foundation-research.md</code>.</p>
</section>
<nav class="docs-pagination" aria-label="Documentation pages">
<a href="/docs/security"><small>Previous</small><span aria-hidden="true">←</span> Security</a>
<a href="/examples/components"><small>Next</small>Component catalog <span aria-hidden="true">→</span></a>
</nav>
{% endblock %}