Home / Docs / Developer docs / Design system and tokens
Design system and tokens
ModScrim looks like a practice ground, not a gaming app: wall green and chalk for the field, a hand-hung scoreboard for results, and a coach's whistle for the mark. This page explains the tokens, the contrast gate that protects them and how to use the brand kit. The complete rules are brand/DESIGN.md, written for people and for AI coding agents.
Developer docs. You want to read, run, deploy or change the code. Not what you came for? Find the right docs.
The idea
- The practice field. Wall green is the ground, chalk is the type and the pitch markings.
- The manual scoreboard. Results sit on dark green boards with hung number plates. Numbers are big and condensed; leading zeros are blank plates.
- The whistle. The mark is a coach's whistle with chat's typing dots inside. Whistle orange is the one action colour.
- Practice is always labelled. Card yellow marks anything that is practice.
Colours
| Role | Token | Hex | Use |
|---|---|---|---|
| Wall green | neutral, background-green, text | #123D2E | Hero, nav, closing band; ink on chalk |
| Dugout | surface-green | #0B2A1F | Practice chat and coach's notes panels |
| Scoreboard | scoreboard | #082319 | Scoreboards and linescores |
| Plate | plate | #103528 | Number plates |
| Chalk | background, text-on-green | #EEF1E8 | Light ground; text on green |
| Paper | surface | #FFFFFF | Cards on chalk |
| Whistle orange | primary | #FF6A1F | The mark, the main button, raid numbers, Slipped chips |
| Orange text | primary-text | #B33D00 | Orange as text on chalk |
| Card yellow | secondary | #FFD23F | Practice labels, Foul chips, focus ring on green |
| Moss | text-muted | #4E6358 | Secondary text on chalk |
Outcomes each have one meaning and always carry a word: Caught (green chip), Slipped (orange chip) and Foul (yellow chip, the yellow card). There is no Twitch purple anywhere.
Type
| Face | Used for |
|---|---|
| Big Shoulders (weight 800) | Headlines, scoreboard numbers, drill-plan times |
| Atkinson Hyperlegible Next | Body text, buttons, navigation, labels |
| Atkinson Hyperlegible Mono | The practice chat and blocked terms only. It keeps look-alike characters such as v1ews and f0ll0w distinct, which is exactly the trick raid spam relies on |
All three are self-hosted as variable woff2 in brand/fonts/ (copies in app/public/fonts/ and site/fonts/) with their SIL Open Font Licence files.
Where the tokens live
| File | Role |
|---|---|
design/tokens.css, tokens.json, palette.json | Generated by the bundled colour skill from the brand colours. Do not hand-edit; re-run the skill |
design/tokens.override.css | Every ModScrim deviation from the generated baseline: semantic roles mapped onto the brand primitives, measured hover fills, the action border |
site/css/tokens.css, site/css/tokens.override.css | Copies for the website, because Cloudflare Pages publishes only site/ |
app/src/main.tsx | Imports the two design/ files for the app |
Semantic variables such as --color-bg, --color-text and --color-action are what components use. Colour comes from tokens only.
The contrast gate
Contrast follows WCAG 2.2: body text 4.5:1, large text 3:1 and meaningful non-text 3:1. Measured pairs from the brand file include chalk on wall green at 10.6:1, moss on chalk at 5.7:1, deep green on whistle orange at 5.4:1 (button labels) and deep green on card yellow at 10.7:1.
Whistle orange as a fill on chalk is only 2.5:1, below the 3:1 non-text bar. That is the one documented exemption, and it holds only because an orange control is identified by its label and its border, not by the fill alone. Two scripts protect it:
| Command | What it enforces |
|---|---|
npm run contrast:check | The generated tokens pass the validator with no exemptions, and the shipped palette fails on exactly the documented pairs (orange on chalk, orange on white), no more and no fewer |
npm run lint:tokens | Any rule that fills itself with the action colour must also declare --color-action-border or --color-border-strong |
npm run site:tokens:check | site/css/tokens*.css still match design/ (run npm run site:tokens to copy) |
npm run site:colours | Every colour hand-written in site/ is a token or listed in brand/DESIGN.md |
All four run in CI. To change a colour, change the override file and re-run the gate; do not widen the exemption without recording why.
The brand kit
| Folder | Contents |
|---|---|
brand/logo/svg, png | modscrim-<lockup>-<tone>. Lockups: horizontal, horizontal with tagline, stacked, mark, small mark, wordmark. Tones: color for light grounds, reverse for green grounds, green, white, black. Plus app icons. All SVG text is outlined |
brand/social | Open Graph image, GitHub preview, Devpost thumbnail, YouTube thumbnail, X header, LinkedIn banner, avatars |
brand/stream | OBS overlays: the practice-drill bar, the scorecard, and a Twitch panel |
brand/fonts | The three typefaces and their licences |
brand/ModScrim-brand-sheet.png | One-page overview |
The README banner, docs/assets/banner.svg, is built from the horizontal-tagline-reverse lockup and the reverse mark with the brand colours.
Logo rules. Keep at least one mark-height of clear space. Below 48 px use the small cut (no ring, no window); at 16 px drop the dots too.
Do and do not
- Do label every screen that shows attack text as practice, in card yellow.
- Do keep one orange button per view; second actions are outline buttons.
- Do write plain, specific copy from the moderator's side.
- Do not use Twitch purple, the Twitch logo, or wording that suggests Twitch made or endorses ModScrim.
- Do not render attack text anywhere outside a labelled practice chat, including marketing screenshots.
- Do not put orange text on chalk (use
#B33D00), add gradients or glitch effects, or fade sections in on scroll. Motion answers actions, andprefers-reduced-motionturns it off.