Apply the rollercoaster.dev neo-brutalist design language to Vue/Tailwind and React Native components. Use when creating/reviewing components, fixing styles, or auditing visual consistency. Invoke with "review my component", "check my styles", "apply design system", or "audit design".
This skill defines how to build and review components using the rollercoaster.dev neo-brutalist design system across two platforms:
apps/openbadges-system) — CSS utility classes in main.csspackages/openbadges-ui) — shadowStyle() helper in src/styles/shadows.tsBold, confident, accessible. Drama comes from structure (thick borders, hard shadows, tight type) — not from color saturation or visual noise. The base is calm; accents are highlights, never backgrounds.
The openbadges-system app defines ready-made CSS classes in src/client/assets/styles/main.css that encode the full neo-brutalist system. over composing inline Tailwind utilities — they ensure consistent borders, shadows, radii, and focus states.
Source: apps/openbadges-system/src/client/assets/styles/main.css
<!-- CORRECT — uses .card class -->
<div class="card p-6">...</div>
<!-- WRONG — inline utilities miss 2px border, hard shadow, sm radius -->
<div class="bg-card shadow-sm rounded-lg p-6 border border-border">...</div>
The .card class provides:
bg-card backgroundborder: var(--ob-border-width-medium) solid var(--ob-border) — 2px borderborder-radius: var(--ob-border-radius-sm) — sharp cornersbox-shadow: var(--ob-shadow-hard-md) — hard offset shadowRelated: .card-header, .card-body, .card-footer for internal structure.
<!-- Primary button -->
<button class="btn btn-primary w-full">Sign in</button>
<!-- Secondary button -->
<button class="btn btn-secondary">Cancel</button>
The .btn class provides:
border: var(--ob-border-width-medium) solid transparent — 2px borderborder-radius: var(--ob-border-radius-sm) — sharp cornersbox-shadow: var(--ob-shadow-hard-sm) — hard offset shadow:focus-visible outline with var(--ob-border-color-focus) and var(--ob-shadow-focus).btn-primary adds: bg-primary text-primary-foreground hover:bg-primary-dark + visible border
.btn-secondary adds: bg-card text-card-foreground hover:bg-muted + visible border
<!-- CORRECT -->
<input class="form-input" />
<!-- WRONG — inline utilities miss 2px border, hard shadow, focus-visible -->
<input class="block w-full px-3 py-2 border rounded-md shadow-sm ..." />
The .form-input class provides:
background-color: var(--ob-form-background)border: var(--ob-border-width-medium) solid var(--ob-form-input-border) — 2px borderborder-radius: var(--ob-border-radius-sm) — sharp cornersbox-shadow: var(--ob-shadow-hard-sm) — hard offset shadow:focus-visible outline with offset and focus shadow<!-- CORRECT — semantic alert classes -->
<div class="alert alert-success">...</div>
<div class="alert alert-error">...</div>
<div class="alert alert-warning">...</div>
<div class="alert alert-info">...</div>
<!-- WRONG — inline utilities miss 2px border, sm radius, token colors -->
<div class="mb-6 p-4 bg-destructive-light border border-destructive rounded-md">
...
</div>
The .alert class provides:
padding: 1remborder: var(--ob-border-width-medium) solid transparent — 2px borderborder-radius: var(--ob-border-radius-sm) — sharp cornersEach variant (.alert-success, .alert-error, .alert-warning, .alert-info) sets:
background-color: var(--ob-color-{type}-light)color: var(--ob-{type})border-color: var(--ob-{type})When CSS classes don't cover a use case, use these Tailwind token colors (NOT raw colors like gray-200, blue-600):
| Token | Tailwind class | CSS variable |
|---|---|---|
| Background | bg-background | --ob-background |
| Text | text-foreground | --ob-foreground |
| Muted text | text-muted-foreground | --ob-muted-foreground |
| Primary | bg-primary, text-primary | --ob-primary |
| Primary text on bg | text-primary-foreground | --ob-primary-foreground |
| Primary hover | hover:bg-primary-dark | --ob-primary-dark |
| Card bg | bg-card | --ob-card |
| Muted bg | bg-muted | --ob-muted |
| Border | border-border | --ob-border |
| Input border | border-input | --ob-input |
| Focus ring | ring-ring | --ob-ring |
| Destructive | text-destructive | --ob-destructive |
| Success | text-success | --ob-success |
| Warning | text-warning | --ob-warning |
| Info | text-info | --ob-info |
<div class="shadow-hard-sm">...</div>
<!-- chips, small elements -->
<div class="shadow-hard-md">...</div>
<!-- cards, buttons -->
<div class="shadow-hard-lg">...</div>
<!-- modals, dropdowns -->
<div class="shadow-focus">...</div>
<!-- focus states -->
NEVER use shadow-sm, shadow-md, shadow-lg — these are Tailwind's default blurred shadows.
<div class="rounded-sm">...</div>
<!-- cards, containers, alerts (2px) -->
<div class="rounded-md">...</div>
<!-- buttons, inputs (4px) -->
NEVER use rounded-lg or rounded-xl on component containers — too round for neo-brutalism.
When reviewing a Vue component for design compliance:
.card, .btn-primary, .form-input, .alert-* before inline Tailwindgray-*, blue-*, red-*, green-*, yellow-* — only tokenstext-white: Use text-primary-foreground or text-card-foregroundshadow-sm/shadow-md: Use shadow-hard-sm/shadow-hard-md or CSS classesrounded-lg: Use rounded-sm for containers, rounded-md for inputs/buttonsborder-2 if not using CSS class: 2px borders on all containersfocus-visible: not focus:: Keyboard-only focus ringsmin-h-[48px] or py-3 on all interactive elementsborderWidth: theme.borderWidth.medium; // 2px — the neo-brutalist standard
Every card, input, button, badge chip, and container uses medium (2px). Use thin (1px) only for subtle internal dividers. Never use default (1px) for containers.
// Canonical source: src/styles/shadows.ts
// (shared.tsx re-exports from shadows.ts for story convenience)
import { shadowStyle } from '../../styles/shadows';
// Usage in StyleSheet.create:
...shadowStyle(theme, 'hardSm') // 2px offset — chips, badges, small elements
...shadowStyle(theme, 'hardMd') // 3px offset — cards, buttons
...shadowStyle(theme, 'hardLg') // 4px offset — modals, hero blocks
// What it expands to (for reference — always use the helper):
{
shadowColor: '#000000',
shadowOffset: { width: 3, height: 3 }, // hardMd example
shadowOpacity: 0.15,
shadowRadius: 0, // ZERO — this is the key
}
NEVER use blurred shadows (shadowRadius > 0). Hard offset = neo-brutalist. The offset creates a crisp printed-poster feel.
Shadow size guide:
hardSm (2px) — small interactive: badge chips, status pills, icon buttonshardMd (3px) — standard interactive: cards, buttons, inputshardLg (4px) — large containers: modals, hero sections, narrative blocksborderRadius: theme.radius.sm; // 2px — cards, containers, chips
borderRadius: theme.radius.md; // 4px — buttons, inputs
borderRadius: theme.radius.pill; // 9999 — status pills, avatars
Radii are deliberately SMALL. This is neo-brutalism — not rounded modern UI. Never use radius.lg (8px) or radius.xl (12px) for component containers. Those are reserved for decorative story previews only.
| Element | Radius |
|---|---|
| Cards, containers, chips | radius.sm (2px) |
| Buttons, inputs | radius.md (4px) |
| Status pills, circular buttons | radius.pill (9999) |
// Headlines — Anybody font, tight letter-spacing
fontFamily: theme.fontFamily.headline; // 'Anybody'
letterSpacing: theme.letterSpacing.tight; // -0.48 (creates density and drama)
fontWeight: theme.fontWeight.black; // '900' — display only
fontWeight: theme.fontWeight.bold; // '700' — headings
// Body — Instrument Sans
fontFamily: theme.fontFamily.body; // 'Instrument Sans'
fontWeight: theme.fontWeight.normal; // '400'
// Labels (uppercase, tracked)
textTransform: "uppercase";
letterSpacing: theme.letterSpacing.wide; // 1.6
fontWeight: theme.fontWeight.bold;
fontSize: theme.size.xs; // 12px
Use the textStyles presets from the theme (theme.textStyles.display, .headline, .title, .body, .caption, .label, .mono) — or the <Text variant="..."> component — rather than assembling typography manually.
// CORRECT — accent as a small highlight
backgroundColor: theme.narrative.climb.bg; // narrative section bg