06 · foundations
16 rules

Rules. What keeps it honest.

Each rule was written after a real bug. The AGENTS.md generator on the home page reads this same list.

color
5 rules
  • do Use a CSS variable for every color

    don't Hardcode a hex in a component. It stops following the theme

  • do Derive brand accents from --fg-brand with color-mix()

    don't Hardcode the violet. It stops reacting when someone picks another theme

  • do Put --fg-on-brand on a brand fill

    don't Reach for a fixed near-white. It fails contrast in 7 of the 12 theme and mode pairs

  • do Color brand text below 24px with --fg-brand-text

    don't Use --fg-brand for small text. It fails AA in 4 of the 12 pairs

  • do Put cards on --bg-card, menus on --bg-surface, dialogs on --bg-overlay

    don't Fill a grid of cards with --bg-surface. It reads as a field of gray

type
2 rules
  • do Size text with the ten scale steps, such as text-mono-sm

    don't Write text-[13px] or Tailwind's text-sm. A test fails on both

  • do Pick the family at the call site: font-serif, font-sans or font-mono

    don't Expect a scale step to set the font. It sets size and leading only

cards
1 rule
  • do Let a row of two halves wrap, and keep each half on one line

    don't Assume it fits. A card clips with no ellipsis, so overflow looks like missing data

motion
4 rules
  • do Trigger entrances with whileInView and once, everywhere

    don't Use animate above the fold. One trigger leaves no judgement call to get wrong

  • do Ask useReducedMotion() in anything animated through JS

    don't Assume the CSS reset caught it. Motion never goes through that block

  • do Keep the real text in the DOM and animate a hidden copy

    don't Grow a string in state. Crawlers and screen readers get an empty heading

  • do Move glows and shines with transform

    don't Rebuild a gradient every frame. It repaints the card and stalls other animations

a11y
2 rules
  • do Hide glyphs such as ◆, $ and // with aria-hidden

    don't Let a screen reader say black diamond suit before every label

  • do Give bare buttons the .focus-ring class

    don't Rely on outline. The reset removes it from every button

icons
1 rule
  • do Import Phosphor from @phosphor-icons/react/dist/ssr in files without use client

    don't Import the package root there. next build fails with an error that names no file

routing
1 rule
  • do Pass your router's link with asChild or linkComponent, and mark the current item with active

    don't Import next/link in a copied component. It stops working outside Next.js

Active theme: entrepta, dark mode.