# BUDDi Design System: Port Kit > A neobrutalist design system built around one metaphor: "The Coach's Whiteboard." Direct, marked up in bold ink, nothing hidden behind polish. This file is a complete, framework-agnostic specification for porting the system into another project. Copy the tokens, the primitives, and the component recipes verbatim; then rebrand by swapping the single accent hue. Every color pairing below meets WCAG 2.1 AA (4.5:1 for text). This is a self-contained port kit. You do not need the original repository. Everything an implementer needs is inline: design tokens, base CSS, and drop-in component CSS. The system uses plain CSS custom properties, so it works with any framework (React, Vue, Svelte, plain HTML) and any styling approach (Tailwind, CSS modules, vanilla CSS). ## What defines this system (read first) The look is neobrutalist: hard ink borders, solid offset shadows with zero blur, a warm paper canvas with a faint dotted grid, heavy type, and a small set of saturated single-tone accents. It reads as candid and sturdy, the opposite of a soft corporate-assistant UI. Key characteristics: - Hard 3px/2px ink borders and solid zero-blur offset shadows on a warm paper canvas. - One tone per hue. No color ever appears in a lighter second version. - A single brand accent held apart from a governed status set (positive, caution, success, info, danger). - Heavy system-sans type: weight (800 vs 500) and size, not typeface variety, build the hierarchy. - Depth is a real behavior: surfaces lift on hover and press inward on click. ## The invariant rules (do not violate when porting) These are the rules that make the system coherent. Keep them exactly. 1. The One-Tone Rule. Every hue is exactly one tone. Never introduce a lighter, tinted, or opacity-faded second version of any color. Depth and separation come from ink borders and offset shadows, not from tints. 2. The Accent-Is-Not-A-Status Rule. The brand accent is the primary CTA only, and stays outside the status set. Success is green, caution is gold, positive is violet, information is blue, errors are red. An action must never look like an alert, and an alert must never look like an action. 3. The Warm-Caution, Cool-Positive Rule. Warm hues warn and cool hues reassure. Caution is gold (warm); positive/on-track is violet (cool). Assign status by temperature. 4. The Weight-Carries-Hierarchy Rule. Build hierarchy from font weight (800 for anything structural, 500 for body) and size, never from adding typefaces or from color. 5. The No-Lighter-Gray Rule. Secondary text stops at charcoal (#3a3a3a). Never use a lighter gray to de-emphasize copy; contrast is a hard commitment. 6. The Zero-Blur Rule. Shadows are solid ink with no blur and no transparency. A blurred or soft drop-shadow is off-system. 7. The Depth-Is-Motion Rule. Interactive surfaces lift on hover and press in on active. A bordered surface that never moves under interaction feels dead. ## Design tokens (CSS custom properties) Framework-agnostic. Paste into your global stylesheet on `:root`. These are the normative source of truth; reference them everywhere via `var(--token)`. ```css :root { /* Neutrals and canvas */ --color-ink: #0a0a0a; /* all text, all borders, all offset shadows */ --color-charcoal: #3a3a3a; /* secondary text / helper copy (alias: --color-mute) */ --color-mute: #3a3a3a; /* same value as charcoal; used as the muted-text role */ --color-paper: #fff9f5; /* app canvas background (warm off-white) */ --color-paper-dark: #fff0e8; /* dotted-grid dots, default badge fill, code chips */ --color-panel: #ffffff; /* card / input / elevated-surface fills */ --color-line: #0a0a0a; /* border role; same value as ink */ /* Accents: ONE tone each, never a lighter version */ --color-magenta: #c4177a; /* THE brand accent + primary CTA */ --color-violet: #7c3aed; /* secondary CTA + "positive / on-track" */ --color-gold: #ffb800; /* header band + "caution" (fill only, never text) */ /* Semantic status roles (map onto the hues above + two jewel tones) */ --color-accent: #c4177a; /* = magenta: brand + primary action */ --color-good: #7c3aed; /* = violet (cool): positive / on-track */ --color-warn: #ffb800; /* = gold (warm): caution (use as fill, not text) */ --color-success: #15803d; /* green: confirmed / saved / passed */ --color-info: #2563eb; /* blue: neutral information / tips */ --color-danger: #dc2626; /* red: errors only, never the CTA */ --color-disabled: #d6d2cc; /* neutral gray for disabled controls, never a washed accent */ } ``` If the target project uses Tailwind CSS v4, declare the same tokens inside an `@theme` block instead of `:root` so Tailwind also generates utility classes (`bg-magenta`, `text-ink`, etc.): ```css @import "tailwindcss"; @theme { --color-ink: #0a0a0a; --color-charcoal: #3a3a3a; --color-paper: #fff9f5; --color-paper-dark: #fff0e8; --color-panel: #ffffff; --color-line: #0a0a0a; --color-mute: #3a3a3a; --color-magenta: #c4177a; --color-violet: #7c3aed; --color-gold: #ffb800; --color-accent: #c4177a; --color-good: #7c3aed; --color-warn: #ffb800; --color-success: #15803d; --color-info: #2563eb; --color-danger: #dc2626; --color-disabled: #d6d2cc; } ``` ## Base styles (canvas, focus, primitives) Paste after the tokens. These four `.nb*` classes plus `.kicker` are the whole structural vocabulary of the system. ```css html, body, #root { height: 100%; } body { margin: 0; background: var(--color-paper); color: var(--color-ink); font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-weight: 500; /* body default sits heavier than the web norm */ /* faint dotted grid: the "whiteboard" texture */ background-image: radial-gradient(var(--color-paper-dark) 1.5px, transparent 1.5px); background-size: 22px 22px; } h1, h2, h3 { letter-spacing: -0.01em; } /* Themed browser surfaces: keyboard focus ring, selection, native accent */ :focus-visible { outline: 3px solid var(--color-ink); outline-offset: 2px; } ::selection { background: var(--color-gold); color: var(--color-ink); } :root { accent-color: var(--color-magenta); } button, [role="button"], a[href], summary { cursor: pointer; } button:disabled { cursor: not-allowed; } /* --- Neobrutalist primitives: hard borders + solid offset shadows --- */ .nb { border: 3px solid var(--color-ink); box-shadow: 5px 5px 0 0 var(--color-ink); } /* panels / cards */ .nb-sm { border: 2px solid var(--color-ink); box-shadow: 3px 3px 0 0 var(--color-ink); } /* buttons / badges / small surfaces */ /* Depth is motion: lift on hover, press in on click */ .nb-press { transition: transform 0.05s ease, box-shadow 0.05s ease; } .nb-press:hover { transform: translate(-1px, -1px); box-shadow: 6px 6px 0 0 var(--color-ink); } .nb-press:active { transform: translate(3px, 3px); box-shadow: 2px 2px 0 0 var(--color-ink); } /* Uppercase eyebrow / label voice */ .kicker { text-transform: uppercase; letter-spacing: 0.08em; font-weight: 800; font-size: 0.7rem; } ``` ## Typography scale One system-sans stack, set heavy. There is no display face and no webfont. Hierarchy is weight plus size. - Display: weight 800, 1.875rem (30px), line-height ~1.1, tracking -0.01em. Biggest headings / hero. - Headline: weight 800, 1.5rem (24px). The common page H1. - Title: weight 800, 1.25rem (20px) for H2, 1.125rem (18px) for H3. Section headings. - Body: weight 500, 0.875rem (14px), line-height 1.5. All body copy. Muted body uses `color: var(--color-charcoal)`. - Label (kicker): weight 800, 0.7rem (11px), uppercase, letter-spacing 0.08em. Eyebrows and button labels. ## Shapes Rounded but not soft. Small radii against thick straight borders is the signature. - Badges/chips: 4px (`border-radius: 4px`). - Buttons and inputs: 6px. - Cards and panels: 8px. - Circular only (spinner, avatar tiles): 9999px. Borders are always ink and always visible. There are no borderless raised surfaces. ## Component recipes (drop-in CSS) Self-contained. Each class expands to literal CSS and references only the tokens above, so it works in any project that has pasted the tokens and `.nb*` primitives. Class names are prefixed `ds-` to avoid collisions. ```css /* Buttons: nb-sm + nb-press, 6px corners, uppercase heavy label */ .ds-btn { display:inline-block; border:2px solid var(--color-ink); box-shadow:3px 3px 0 0 var(--color-ink); border-radius:6px; padding:8px 16px; font-size:0.875rem; font-weight:800; text-transform:uppercase; letter-spacing:0.025em; cursor:pointer; transition:transform .05s ease, box-shadow .05s ease; } .ds-btn:hover { transform:translate(-1px,-1px); box-shadow:6px 6px 0 0 var(--color-ink); } .ds-btn:active { transform:translate(3px,3px); box-shadow:2px 2px 0 0 var(--color-ink); } .ds-btn:focus-visible { outline:3px solid var(--color-ink); outline-offset:2px; } .ds-btn-primary { background:var(--color-magenta); color:#fff; } /* primary action */ .ds-btn-violet { background:var(--color-violet); color:#fff; } /* affirmative secondary */ .ds-btn-ghost { background:var(--color-panel); color:var(--color-ink); } /* tertiary / back */ .ds-btn:disabled { background:var(--color-disabled); color:var(--color-charcoal); box-shadow:none; transform:none; cursor:not-allowed; } /* flat gray, no fade */ /* Badge: labeled status chip; color and word always agree */ .ds-badge { display:inline-block; border:2px solid var(--color-ink); border-radius:4px; padding:2px 8px; font-size:0.75rem; font-weight:700; background:var(--color-paper-dark); color:var(--color-ink); } .ds-badge-gold { background:var(--color-gold); color:var(--color-ink); } .ds-badge-violet { background:var(--color-violet); color:#fff; } .ds-badge-success { background:var(--color-success); color:#fff; } .ds-badge-info { background:var(--color-info); color:#fff; } /* Card: primary content surface (nb panel elevation) */ .ds-card { background:var(--color-panel); color:var(--color-ink); border:3px solid var(--color-ink); box-shadow:5px 5px 0 0 var(--color-ink); border-radius:8px; padding:20px; } .ds-card-magenta { background:var(--color-magenta); color:#fff; } .ds-card-violet { background:var(--color-violet); color:#fff; } .ds-card-gold { background:var(--color-gold); color:var(--color-ink); } /* Input: 2px ink border that shifts to accent on focus */ .ds-input { width:100%; box-sizing:border-box; background:var(--color-panel); color:var(--color-ink); border:2px solid var(--color-ink); border-radius:6px; padding:8px 12px; font-size:0.875rem; outline:none; } .ds-input::placeholder { color:var(--color-charcoal); } .ds-input:focus { border-color:var(--color-magenta); } .ds-input:focus-visible { outline:3px solid var(--color-ink); outline-offset:2px; } /* Status notes: one hue each, none borrows the accent */ .ds-note { border:2px solid var(--color-ink); box-shadow:3px 3px 0 0 var(--color-ink); border-radius:6px; padding:8px 12px; font-size:0.875rem; color:#fff; } .ds-note-success { background:var(--color-success); font-weight:700; } .ds-note-info { background:var(--color-info); font-weight:600; } .ds-note-error { background:var(--color-danger); font-weight:700; } /* Spinner: ink ring with an accent top arc */ .ds-spinner { display:inline-block; height:0.875rem; width:0.875rem; border:3px solid var(--color-ink); border-top-color:var(--color-magenta); border-radius:9999px; animation:ds-spin 0.7s linear infinite; } @keyframes ds-spin { to { transform: rotate(360deg); } } ``` Color-on-fill pairing (memorize this): white text on magenta, violet, success, info, and danger; ink text on gold and on the default badge. Gold and the default badge are the only ink-text fills. Gold is never used as text. ## App shell pattern A single centered column governs every screen: `max-width: 48rem` (Tailwind `max-w-3xl`) with 16px side gutters, holding a header, a routed main region, and a footer. The header is a full-bleed gold band with a 3px ink bottom border, containing the same centered column. Vertical rhythm inside a page is a 16px stack; major documentation sections use a 40px stack. Card interiors use 20px padding. The layout is narrow and reading-first. Responsive behavior is minimal: the column narrows with the viewport, and helper grids inside cards collapse to one column below 640px. ## How to rebrand for a new project The system is designed to be reskinned by changing one hue. To port with a different brand identity: 1. Replace `--color-magenta` (and its alias `--color-accent`) with the new brand hue. Pick a tone dark enough that white text on it clears 4.5:1 (test it). Everything that reads as "brand" follows automatically. 2. Keep the status set intact unless you have a strong reason. If you must recolor, preserve rule 3 (warm caution, cool positive) and re-verify every text-on-fill pairing against 4.5:1. 3. Do not add a second tone of any hue. If you need visual variety, use the ink borders, the offset shadows, the paper vs panel surfaces, and weight, not tints. This is the rule most ports get wrong. 4. Keep the warm paper canvas and dotted grid, or swap to a different single flat canvas color; do not introduce gradients. 5. Preserve the `.nb`, `.nb-sm`, and `.nb-press` primitives byte-for-byte. They are the structural identity. If you change border widths or shadow offsets, change them everywhere consistently and keep zero blur. ## Accessibility floor (non-negotiable) WCAG 2.1 AA is a hard requirement, not a nice-to-have. - Every text-on-fill pairing above clears 4.5:1. Re-verify after any color change. - Keyboard focus is always visible via the global 3px ink `:focus-visible` outline. Never remove it. - Status is always conveyed by a label plus color, never color alone. - Disabled controls use the flat gray fill, never an opacity fade (faded text fails contrast). ## Do and Don't (quick reference) Do: - Border every raised surface with ink and pair it with the matching solid offset shadow. - Put `.nb-press` on anything interactive. - Hold body at weight 500 and structural headings at weight 800. - Keep the accent for the brand and primary CTA only; use the status set for meaning. - Put ink text on gold and the default badge; white text on all other fills. Don't: - Introduce a lighter/tinted/faded second version of any hue. - Use blurred, soft, or semi-transparent shadows. - Use gold as text, or use the brand accent for errors, or red for actions. - De-emphasize text with a gray lighter than #3a3a3a. - Add gradients, ambient glows, or pastel tints; that soft corporate-assistant look is the anti-reference.