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