Design system
Flashlights has one look, fixed for every instance, and one colour an instance chooses. This page says how that design system maps onto the code.
Where things live
| Piece | Path |
|---|---|
| Entry stylesheet | portal/static/css/flashlights.css |
| Tokens (colours, type, spacing, effects, base) | portal/static/css/tokens/*.css |
| Fonts and the icon font, with licences | portal/static/fonts/ |
| The instance's accent | /theme.css, rendered from the environment by portal/branding.py |
| Bootstrap re-themed and the portal's components | portal/static/css/portal.css |
| Flashlights' own mark | portal/static/img/flashlights/, templates/portal/_flashlights_mark.html |
base.html loads Bootstrap, then flashlights.css, then theme.css, then
portal.css, all from the instance's own /static/: Bootstrap is
vendored like every other library, and nothing on a page is fetched from a
third party (Third-party code and services).
Tokens and variables
The tokens are CSS custom properties: --night-950 to --night-100,
--paper, --beam-200 to --beam-700, the status colours (--ok-*,
--warn-*, --stop-*) and the semantic aliases (--surface-page,
--text-strong, --border-subtle, and so on). Type is --font-display
(Bricolage Grotesque), --font-body (Atkinson Hyperlegible Next) and
--font-mono (Atkinson Hyperlegible Mono), with the roles --type-display,
--type-h1 to --type-h4, --type-body, --type-small, --type-eyebrow
and --type-mono. Spacing is a 4px base (--space-1 to --space-9), radii
are --radius-sm 4, --radius-md 8 and --radius-lg 14, controls are
--control-h-md 44px tall.
The accent is the only variable an instance changes: --accent,
--accent-hover, --accent-press, --accent-ink (the accent as text on
Paper) and --text-on-accent (Night or white, chosen by contrast). The
older --brand-* names and Bootstrap's --bs-primary alias it.
Rules that hold
- Borders over shadows. Cards are white with a 1px Night 200 border and
radius 14; the one important element on a page gets a 2px Night border
(
.card-strong). An interactive card lifts 2px with the hard shadow. - Controls have 2px borders, radius 8 and are 44px tall. Focus is the
two-tone ring (
--ring-focus). - Status always pairs colour with an icon and a word: alerts draw their icon, chips carry a label, checklist boxes carry a glyph.
- Beam is never text on Paper; accent-coloured text uses
--accent-ink. - Night surfaces (navbar, footer, hero, a live session) carry the beam
glow (
--beam-glow), tinted by the accent. - Icons are Lucide, as
<i class="icon-<name>" aria-hidden="true">, 1.1em next to text;icon-fw,icon-xs,icon-lgandicon-2xsize them. Icon-only buttons need anaria-label. - Dates in chips are mono and day-first with the month in capitals,
12 NOV, and schedules state their time zone.
Components and their classes
| Spec | In the code |
|---|---|
| Navbar | base.html; .edition-chip, .avatar-initial, the active tab's underline |
| SideRail | base_sidebar.html and _sidebar_item.html; .app-sidebar .nav-pills |
| Breadcrumbs, Tabs | .breadcrumb, .nav-tabs |
| Toast, Alert, Progress | _toast_messages.html and _toast.html; .alert-* incl. .alert-beam; .progress |
| Input, Select, Checkbox, Switch | .form-control, .form-select, .form-check, .form-switch; allauth's elements in templates/allauth/elements/ |
| Card, StatCard | .card, .card-strong, .stat-card |
| AttentionItem | .attention-list, .attention-item, .attention-tile |
| ChecklistItem | speakers/_checklist_item.html; .checklist-* |
| SessionCard, ScheduleCard, GlanceTile | .session-card, .schedule-card, .glance-tile |
| PersonRow | portal/people.py (person_cell, status_chip); .person-*, .status-chip |
| Wordmark | _flashlights_mark.html; .flashlights-lockup |
| Eyebrow | .eyebrow |
Voice
Plain, warm, specific. "We" are the organizers, "you" is the reader; never "the user". Sentence case everywhere, buttons start with a verb, counts lead in bold, no exclamation marks, no emoji. Gender-neutral wording.