Guidelines
Practical rules that keep the ONCE interface consistent. These apply to both human developers and AI agents writing code in the codebase.
Animation
Motion is kept minimal and purposeful — only on hover and focus.
Allowed Transitions
| Pattern | Tailwind | Usage |
|---|---|---|
| Color change | transition-colors | Links, buttons, icon tints |
| General | transition-all duration-300 | Cards (hover lift + border change) |
What to avoid
- No entrance animations (fade-in, slide-up) on page load
- No looping animations except approved status motion (Early Access badge pulse and AI thinking loader)
- No parallax scrolling or scroll-triggered effects
- No motion that could trigger vestibular discomfort
Hover Lift
Cards use a subtle upward shift on hover:
.card:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(58, 69, 92, 0.08);
}The lift is 1px — not 2px or 4px. Keep it barely perceptible.
Dark Mode
ONCE is a dark-first design system. All components are designed for dark backgrounds first, then adapted for light mode.
Implementation
- The docs site uses
html.darkclass toggling (Nextra’s built-in dark mode) - The dev-app uses Tailwind’s
dark:variant - CSS custom properties swap between light and dark palettes via
html.dark { ... }
Rules
- Always test new components in dark mode first
- Use
var(—text-strong)andvar(—text-subtle)instead of hardcoded colors - Backgrounds should use surface tokens, not raw hex values
- Avoid pure white (
#fff) on dark backgrounds — use—once-whitewith opacity
Naming Conventions
CSS Variables
- Brand colors:
--once-{name}(e.g.,--once-blue,--once-bg) - Utility colors:
--mr-{name}(e.g.,--mr-approval) - Surface tokens:
--surface,--surface-muted,--surface-strong - Text tokens:
--text-strong,--text-subtle - Font tokens:
--font-sans,--font-display,--font-mono
Tailwind Classes (dev-app)
- Color utilities match the variable name:
once-blue,mr-warning - Use with standard prefixes:
bg-once-blue,text-once-white,border-once-blue/20
CSS Classes (docs)
- Component classes are lowercase, hyphen-separated:
card-grid,info-box,api-endpoint - State classes use semantic names:
early-access-badge,patron-badge - Design system preview classes use
ds-prefix:ds-swatch,ds-btn
Accessibility
Contrast
- Primary text on
--once-bgmust meet WCAG AA (4.5:1 ratio minimum) --once-blue-light(#6B7A99) is used instead of--once-bluefor text on dark backgrounds, because the base blue does not pass contrast checks alone- Utility colors were chosen to pass AA on both light and dark backgrounds
Focus States
- Interactive elements must have visible focus indicators
- Use
outlineorbox-shadowfor focus rings — notborder, which shifts layout - Default browser focus styles are acceptable if they remain visible on
--once-bg
Reduced Motion
Respect prefers-reduced-motion for the badge pulse animation:
@media (prefers-reduced-motion: reduce) {
.early-access-badge {
animation: none;
}
.early-access-badge::before {
animation: none;
}
}For AI Agents
If you are an AI agent working in this codebase:
- Always use CSS variables (
var(—once-blue)) — never hardcode hex values - Use the opacity scale for text hierarchy — do not invent new colors
- Keep
font-familyto Manrope and JuliaMono — no other typefaces - Cards get
border-radius: 12px, code blocks get8px, inline code gets4px - Test your output in dark mode. If it looks wrong in dark, it’s wrong
- When in doubt, follow existing patterns in
globals.css