Skip to content

Principles

A system for internal admin tooling. Operator UX over polish, platform features over JavaScript, one contract across both flavors.

Operators sit in these screens all day. The defaults reflect that:

  • Information density — compact spacing, smaller defaults, tabular layouts. Don’t pad components to feel “premium.”
  • Operator UX — keyboard affordances, predictable focus, fast scanning, low chrome.
  • Clarity over polish — legible type, honest borders, restrained color. Decorative gradients and oversized hero spacing belong in a different system.
  • Predictable primitives — fewer variants, consistent names, no surprises. A <Button> does what <button> does.

When in doubt: would this make a 12-row form on a busy admin screen easier or prettier? Pick easier.

Reach for modern HTML and CSS before reaching for JavaScript. Admin users run current browsers — no legacy budget, no polyfills, no graceful fallbacks. If a behavior can be expressed declaratively, it should be.

  • Animation — CSS transition, @keyframes, @starting-style, transition-behavior: allow-discrete.
  • Disclosure and overlays<dialog> + showModal(), the popover attribute, anchor-name / position-anchor.
  • Accordions<details> + <summary>, with ::details-content for animated open/close.
  • Form state:has(), :user-valid, :user-invalid, :placeholder-shown, accent-color, field-sizing: content.
  • Layout — container queries, subgrid, aspect-ratio, logical properties, text-wrap: balance / pretty.
  • Scrollposition: sticky, scroll-snap-*, overscroll-behavior, scroll-margin-*.
  • Color and theminglight-dark(), color-mix(), relative color syntax, @property.

JavaScript shows up when behavior is genuinely stateful, needs data fetching, or has no declarative equivalent. In those cases, lean on Base UI primitives instead of rolling your own.

Both packages ship the same class names. @aortl/admin-css defines .btn, .input, .field, … with Tailwind @apply. @aortl/admin-react wraps Base UI primitives in thin components that emit those same classes via clsx. Vanilla HTML and React render identical DOM.

This shapes the rest of the system:

  • Adopting one flavor doesn’t lock out the other — mix freely on the same page.
  • The CSS is the source of truth. Visual changes happen there, and React inherits them automatically.
  • Class names are part of the public API. Renaming .btn-primary is a breaking change.

Naming pattern: <base> + <base>-<variant> + (optional) <base>-<size> + (optional) <base>-<modifier>. Sizes use sm / md (default, omitted) / lg.

Colors live in two layers, both registered with Tailwind’s @theme:

  1. Palette — Flexoki ramps (--color-blue-600, --color-base-50, …). Absolute tones, identical in light and dark mode.
  2. Semantic — purpose-named aliases (--color-primary, --color-surface, --color-danger, …) point at palette tones via light-dark().

Components only ever reference semantic tokens. That’s what makes reskinning work — override --color-primary and every component follows. Spacing, radii, and shadows use Tailwind’s built-in scales (p-4, rounded-lg, shadow-xs).

See Colors for the full token catalog and Customize for override patterns.