Paper, ink and one green.
A small, opinionated design system for calm interfaces: a token set, a CSS component library, and a few dependency-free behaviours. Light and dark from the same tokens. No build step.
One sentence first
Every screen opens with a headline that states the situation in plain words. Counts live inside sentences, never as big numbers with tiny labels.
Lines, not boxes
Hairlines separate content. Cards are for things that float or take input: a command bar, a reply box, a dialog, a toast.
Nothing moves until you do
Motion answers an action and nothing else. Reduced-motion preferences are respected everywhere.
Install
Copy tokens/, css/ and js/ into your project, or point at them from this repository. Load Inter and Inter Tight from Google Fonts (both are open fonts), then:
<link rel="stylesheet" href="css/swiss-moss.css">
<script src="js/swiss-moss.js" defer></script>
Every class is prefixed with sm-. Every colour, size and radius is a custom property prefixed --sm-, so your own CSS can use them too. Building with an agent? Point it at DESIGN.md: tokens, rules and a component index on one page. Designing by hand? Open design/swiss-moss.figcraft in figCraft. Dark mode follows the system by default; add data-theme="dark" or "light" on <html> to force one, or use the buttons above, which persist the choice.
Tokens
Switch the theme above to see the second set. Both live in tokens/tokens.css, and tokens/tokens.json holds the same values for tooling. line is the hairline for dividers and cards; control-line is the stronger border for inputs, selects, checkboxes, switch tracks and range tracks, at 3:1 against the page so a control is visible without its fill. npm run check verifies every text and control pair in both themes.
Colour
Spacing, on a 28px rhythm
Radius and shadow
Typography
Inter Tight for display and item titles, Inter for everything else. Sentence case everywhere; no all-caps labels.
Display xl
Display l, the screen headline.
Display m for secondary heroes
Title, 28px
Heading, 20px
Item, 19px: the title of a row, the most read style
Lead, 17px, for an introduction under a headline.
Body, 15px. The default. Line height 1.55 so paragraphs breathe.
Small, 14px, for secondary lines.
Caption, 13px, muted, for who and when.
Key, 12px, for tiny labels and keyboard hints
<h1 class="sm-display-l">Seven things need you.</h1> <p class="sm-item">Rotate the production keys before the audit</p> <p class="sm-caption">Priya, 12 min</p>
Chips, tags, badges, labels
Chips are interactive: filters you toggle or values you remove. Tags are static descriptors. Badges are counts. Labels name fields.
Chips: toggle, removable, with avatar
Tags
Badges and labels
Inputs
Every native type, styled once. Fields carry their label, hint and error; add data-sm-validate to a field to show the browser's own validity on blur.
Tag inputs
A field that turns what you type into chips: email recipients, labels, keywords, numbers. Enter, comma, paste and blur all commit a value; Backspace removes the last chip; invalid values are kept but marked so people can fix them. Add data-sm-suggest for a typeahead list and single for exactly one value.
Read the values with element.getValues() or from the hidden input (comma separated); set them with element.setValues([...]); listen for sm:change.
Selection controls
Feedback
Alerts
Toast, dialogs, tooltip
Progress, spinner, skeleton, empty state
New items land here as they arrive. Everything else waits quietly in the Feed.
Data display
Cards and stats
Plain card
For things that float or take input.
Tinted card
For the thing being acted on, or a note.
Lifted card
For something being dragged or a popover.
List rows, the queue pattern
- Rotate the production KMS keys before the audit windowPLAT-412, Priya Nair12 min
- Checkout latency spike, root cause still open#incident-1002, Tomas Weber25 min
- Observability contract renewal: counter-offer attachedEmail, Mara Lindqvist1 h
Table, avatars, accordion
| Name | Group | Connected | Last sync | |
|---|---|---|---|---|
| AFAna Ferreira | platform-admins | Google Slack Jira | 2 min ago | |
| PNPriya Nair | platform | Google Slack | 6 min ago | |
| LOLeah Okafor | platform | Jira expired | 1 h ago |
What happens to the original email when I mark it done?
Can I undo?
More inputs
Steppers, one-time codes, passwords, money, phone numbers, dates and multi-select. Each is plain HTML first; the behaviour is opt-in through a data-sm-* attribute.
Number stepper and one-time code
Password with reveal and strength
Money and phone
Calendar, date picker and date range
Multi-select: a menu of checkbox items, the badge counts the choices
Overlays
Popovers use the native popover attribute, so opening, closing and light dismiss need no script; Swiss Moss only places them under their button. Sheets, drawers and the palette are <dialog> elements and open with data-sm-dialog-open.
Popover and info tip
Sheet, drawer, and a dialog that becomes a drawer on phones
Context menu: right-click the area, or focus it and press Shift+F10
Command palette: press Cmd+K or Ctrl+K
Page structure
Top bar, banner and page header
Labeled divider, carousel, aspect ratio and scroll area
Weekly digest
Monday, 09:00
Reply later
Every afternoon
Set aside
Friday review
Feed
Read when there is time
A scroll area keeps a long list inside a fixed height with a thin scrollbar in the control colour.
Use it for logs, side lists and pickers.
It never hides the scrollbar, so people can see there is more.
Tables and records
Data table: toolbar, sortable columns, row selection and a bulk action bar
| Priya Shah | Active | 12 | 2 Mar 2025 | |
| Tomas Berg | Invited | 3 | 14 Sep 2026 | |
| Leah Okafor | Suspended | 0 | 30 Nov 2024 | |
| Ana Ruiz | Active | 27 | 8 Jan 2026 |
KPI with delta and sparkline, bar list, status
Description list and timeline
- Owner
- Platform team
- Schedule
- Every five minutes
- Token
- sk_live_7f3a…
- Run finished. 214 messages read.
- Slack slowed down. Retried twice.
- Run started.
Code block, truncation and an error state
npm installnpm run checkLong notes stay at two lines until someone asks for more. The rest of the text is still in the page, so search and screen readers find it, and the button below opens the block in place.
The worker did not answer in 10 seconds.
Writing
- Headlines are full sentences with a full stop: "Four replies, one sitting."
- Buttons say what happens: "Approve in Jira", "Send and next", "Revoke all tokens".
- Hints explain consequences in one line: "Dismissing silences all your devices."
- Sentence case everywhere. No exclamation marks, no emoji in the interface.
- Signal colour means something is wrong or irreversible. It never decorates.