Style guide

Everything the site is made of.

One page carrying every part in every state. The rules are in apps/web/app/globals.css and nowhere else: change a token or a class here and it changes on every page, because pages compose these parts rather than restyling them.

The five rules

  1. Every action is a button; every navigation is a link. If it does something — submits, opens the demo, starts the path — it takes .btn. If it takes you somewhere to read, it stays a link.
  2. One page shell. <main> for a reading page, <main className="wide"> for a marketing page, <main className="app"> for the club, and each opens with section.hero or an h1.
  3. Controls are styled by element. An input, select, textarea or button needs no class to look right; classes only say which kind of button it is.
  4. Nothing is styled inline. No colours, sizes or spacing in the page — they belong to a token or a class.
  5. Write to the reader, not about the work. Say what they can do, in the order they would do it, in the second person. Why a thing works the way it does belongs in the wiki — unless they need it to make a choice (what is kept, what needs a licence, what costs nothing). Cut any sentence whose subject is we, the platform or this, and any comparison to a product they may never have used.

Colour

--bgpage
--bg-raisedcard, control
--bg-sunkenband, hover
--inktext
--ink-2secondary text
--ink-3quiet text
--linehairline
--line-strongcontrol border
--primaryaction
--accenthighlight, the mark
--accent-inksignpost line
--okgood
--warncaution
--dangerbad

Every colour is a token, and every token has a dark value that the OS switches. Nothing in a page names a hex.

Roster workspace

Saved.

CallsignRoleMember since
N0CALL Example operatorNet controlSep 7, 2026

Search above the roster; labeled rows on small screens; saving and connection status beside the work.

Type

Heading one, the page’s claim

Heading two, a section

Heading three, a thing within it

The lead: one paragraph under the heading that says what this page is for, in plain words.

Body text sits on a measure of 50rem so a line stays readable. Bold for the word that matters, an underlined link for somewhere to go, and code for a name from the machine.

Muted: the aside, the caveat, the row under a control.

The face is Atkinson Hyperlegible Next, served from this site and nowhere else: its zero is slashed the way a callsign is written — K02ABC reads as K02ABC in running text too — and it was drawn to stay legible at a small size on a phone.

Buttons

A link doing an action takes the same classes, so the two never look different: A link as a primary button

Form controls

A label is a block and its field sits under the text; fields that belong side by side sit in a .row and share the width. A text field and a choice are the same height, so the labels above a row of them line up. Two text-area heights, three rows and six, and no cols: the width comes from the layout.

Rows of controls

The buttons under a list item sit in one .row-actions line that wraps when it must. A row keeps the controls a thumb lands on every time and puts the rest one tap away behind .row-more — nothing is removed, and no more than four sit in the open. There is no button that reads as a link: a control that removes a row or dismisses a hint is a small button, not underlined text.

States

Empty: what is missing, and the one thing to do about it. Never a blank list.

Pills carry status: On the air Needs height Expires in 40 days Neutral

Cards and grids

A card

One idea, one action, nothing below the fold.

Its action

Another

Cards in a grid are equal in weight; if one matters more it is not a card.

Its action
qsoapp.com/near

Near you · Brooklyn

146.730 · KC2RCTry this one
449.225 · W2ABCNeeds height
Node 51234 · ManhattanOnline · 3 connected

Terrain checked between where you stand and the site.

What is on the air near you, with a plain answer per site: worth trying from a handheld, or not.

A hero with its screen

The signpost line

The claim, with the proof beside it.

A marketing page opens with the screen it promises next to the sentence that promises it, so nobody scrolls to find out whether the thing is real.

qsoapp.com/first-contact

Your first contact

Pick a radioDone
Program itDone
Find a repeater you can reachNow
Make the callNext
Show me repeaters near me
First Contact: five steps from a licence in hand to a logged QSO, each one answered for where you are.

The signpost line above the claim is a sentence in --accent-ink, never a tracked label in capitals; a heading carries its own weight.

Feature rows and the audience list

One claim, one screen

Each row pairs a heading, a sentence and its action with the screen that proves it; the sides alternate. Three claims are three rows, never three equal cards.

Its action
qsoapp.com/near

Near you · Brooklyn

146.730 · KC2RCTry this one
449.225 · W2ABCNeeds height
Node 51234 · ManhattanOnline · 3 connected

Terrain checked between where you stand and the site.

What is on the air near you, with a plain answer per site: worth trying from a handheld, or not.

Call-to-action band

Band

One invitation, one button

Used where a page raises a question another page answers. Never two in a row.

Tables and lists

CallsignClassPaid toRole
N2PYZGeneral01/2027
KB1QRPTechnician11/2026

On a phone a table scrolls inside its own box; on a laptop it behaves as a table. A control inside a cell is the cell’s size, not a form field’s. Dates are US format and distances are in miles on screen; the API answers in metric.

Working on this

A designer changes the tokens at the top of globals.css for colour and scale, the class blocks below them for a part, and this page to see the result. A new page picks parts from here; if something is missing it is added here first, with a name, and then used. The reasoning behind each part is on the Design System wiki page.