Contents

Crab design system

Tokens, components and charts for CrabPHP applications. It looks like the Crab site: one typeface, one size, a dark page, amber for what you can act on. Everything here is plain HTML and CSS; copy the markup under any example.

<link rel="stylesheet" href="css/crab.css">

Colour

Every colour is a token in tokens.css. White carries text at four strengths, amber marks what you can act on, and four status colours always sit beside a word.

Type and space

One family, one size, one line height, two weights. Vertical space is counted in lines and horizontal space in characters, so everything sits on the same grid.

TokenValueUse
--fontThe only typeface
--sizeThe only text size; 14px on phones
--lhOne line: the unit of vertical space
--halfhalf a lineCell padding, gaps inside a component
--gapHorizontal gap between related things
--gutterBetween columns
--measureReading column
--wideApplication content
--navNavigation column
--control1.25 lines; 36px on touch screensHeight of an input or solid button
--touchTap area of a text button on touch screens

Text elements

Plain HTML needs no classes. Headings are uppercase and bold at body size, marked like Markdown.

A third-level heading

Body text at 88% white with a link in amber, strong text in full white, inline code, a highlight and a key such as Ctrl K.

  • Lists use a dash
  • in faint white
  1. Numbered steps
  2. keep their numbers

A note has a thin amber rule on its left.

composer create-project crabphp/crab app

Buttons

A button is bracketed amber text. Use one .solid button for the main action of a view, .quiet for secondary actions and .danger for destructive ones.

Link as button

Segmented

A few choices where one is on. Each button carries aria-pressed.

A native popover: it opens under its button, closes on Escape or a click outside, and needs no script.

Inputs

A .field is a label, a control and an optional help line. Controls are square, a line and a quarter tall, with a border strong enough to see.

Set when the site is created.

Choices

Checkboxes, radios and switches are drawn as text. A switch is a checkbox with role="switch".

Collect
Digest
Site

Groups and chips

An .input-group attaches text or a button to a control. Chips hold a set of values, such as recipients or filters.

https://

Validation

An invalid control takes aria-invalid="true" and an .error line that says what to fix.

A domain cannot contain spaces.
Shown next to your address in the inbox.

A whole form

https://
Only page views from this domain are counted.

Tables

The default table is a thin grid. Numbers align right with tabular figures. Wrap a wide table in .table-wrap so it scrolls instead of shrinking.

Sites in this workspace
Status
blog.crabphp.comReceiving8,20438%Open
crabphp.comReceiving48,91241%Open
docs.crabphp.comNo data for 2 days21,37727%Open
shop.example.comPaused00%Open
4 sites78,49337%

Add .plain for rows without a grid, and .compact for dense data.

EventWhenDuration
queue:work started10:42:0712 ms
mail.send to [email protected]10:42:09184 ms
schedule:run10:43:003 ms

Stats

Headline numbers. The value is bold, not bigger. A change shows its direction with an arrow and says what it is compared with.

Visitors
48,912
12.4% vs last week
Page views
131,070
8.1% vs last week
Bounce rate
41%
3 points vs last week
Visit time
2m 14s
no change

Badges

State is a word with a coloured mark in front of it. The word carries the meaning; the colour only helps you scan.

Delivered Deferred Bounced Queued New Draft 128

Rows

A list you pick from: messages, events, jobs. The current row has an amber edge; unread rows are bold with a star.

Definitions

A term beside its value: settings, a message envelope, a list of commands.

From
Cua the Crab <[email protected]>
To
[email protected]
Sent
10 Nov 2026, 09:12
Message ID
<[email protected]>

Progress

A thin bar for how much of something is used or done. Say the number next to it.

62%
9.1 GB

Empty

When there is nothing to show, say why and offer the next step.

No visits yet. Add the tracking script to your site and the first visit will show up here within a minute.

Show the script

Chart colour

Seven series colours in a fixed order, chosen so neighbours stay apart for colour-blind readers. Assign them in order and never reuse one for a different series. A one-hue ramp shows magnitude.

Line

Change over time. Hover or use the arrow keys to read a day; every chart also offers its numbers as a table.

Visitors last 30 days
Email activity last 14 days

Columns

Amounts per period. Bars are thin and square, and stacked parts are separated by a gap, not an outline.

Emails sent per day
Visits by source per week

Ranking

Compare named things with a table and an inline .bar. The table already is the data, so nothing else is needed.

PageVisitors
/18,420
/docs/installation11,790
/docs/routing7,552
/benchmarks4,051
/docs/queues1,660

Heatmap

How much, across two dimensions: here, when mail arrives.

Messages received by weekday and hour, UTC

Segments

One whole split into a few parts. The legend names each part and its share.

Devices

Tabs

Visitors, pages and sources for the chosen range.

Pagination

The column on the left of this page. The current item is amber and marked with >; a .count sits at the right.

Notes

A message in the page. Start with the word that says what it is.

Note. Reports use UTC unless the site sets a timezone.
Saved. Your changes are live.
Warning. This site has sent no data for 2 days.

Toasts

A short confirmation that leaves on its own. Put a .toasts region at the end of the page and add each message to it.

<div class="toasts" role="region" aria-label="Notifications" aria-live="polite">
  <div class="toast ok">Message sent. <button type="button" class="quiet">Dismiss</button></div>
</div>

Dialog

The native <dialog>, opened with showModal(). A form with method="dialog" closes it.

Delete crabphp.com?

This removes the site and all of its analytics. It cannot be undone.

Loading

A spinner beside words. It stops for people who ask for reduced motion.

Loading the report

Tooltip

Wrap the target in an element with data-tip. Use it for a hint, never for something people must read.

Pattern: analytics

A Pulse report built from the pieces above: filters in one row, headline numbers, one chart, two rankings.

Receiving
Visitors
48,912
12.4% vs previous 30 days
Page views
131,070
8.1% vs previous 30 days
Bounce rate
41%
3 points vs previous 30 days
Visitors per day
Top pages
PageVisitors
/18,420
/docs/installation11,790
/docs/routing7,552
Top sources
SourceVisitors
Direct21,004
github.com12,187
news.ycombinator.com6,530

Pattern: mailbox

A mail client: a .split with rows on the left and the open message on the right. Compose opens in a dialog.

12 unread

crabphp/framework 0.6.2 has been published

From
Packagist <[email protected]>
To
[email protected]
Sent
Mon 9 Nov 2026, 14:03 Verified sender

A new version of crabphp/framework is available.

Version 0.6.2 was tagged a few minutes ago and is ready to install. Run composer update crabphp/framework to get it.

New message

Pattern: settings

A settings page: tabs, a short form, and a danger zone set apart by a heading and a note.

Notifications
Last saved 2 minutes ago

Danger zone

Deleting the workspace removes every site, mailbox and token in it.