Democracy League OS design system

The system, rendered.

A living inventory of the Democracy League OS design language. Everything here runs on the same package and tokens as the staff shell itself.

Design philosophy

We make components that help people enjoy accessible, meaningful, and thoughtful experiences.

01

Time to joy.

The first render should feel good. Sensible defaults, no configuration required, something useful and beautiful on screen in under a minute.

02

Care is legible.

Spacing, language, behavior, and API design should reveal that someone paid attention, especially in the states few people celebrate.

03

Every state is the component.

Loading, empty, error, disabled, focused, interrupted. These are not edge cases. A component is unfinished until its difficult states feel intentional.

04

Names teach.

The API is pedagogy. Components, props, and variants should reveal the model they belong to. Each new option creates a decision someone must understand, so prefer strong defaults and a few durable concepts over speculative flexibility.

05

Maintenance before replacement.

We maintain what people have trusted us to build upon. Deprecations include reasons, migration paths, and time to adapt. When replacement is necessary, we carry people across.

06

Every surface should sing.

Utility and beauty belong together. Each component should feel coherent, alive, and worth returning to.

Read from the tokens, never restated.

Every value below comes from the generated token bundle (@dlos/design-tokens/demleagueos.json), which the components read as CSS variables. The system ships light only; a dark set is a variable block in the tokens, not a component change. Families in use: Aileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif, Asap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif; 4 self-hosted font faces declared.

Typography is the quiet foundation.

heroAileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif 900 / 72px / 1.2 / -0.02em

Problem solving over political fighting.

displayAileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif 900 / 96px / 0.8 / -0.02em

Problem solving over political fighting.

h1Aileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif 900 / 48px / 56px / -0.02em

Problem solving over political fighting.

h2Aileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif 900 / 32px / 40px / 0

Problem solving over political fighting.

h3Aileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif 900 / 24px / 32px / 0

Problem solving over political fighting.

h4Aileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif 900 / 20px / 26px / 0

Problem solving over political fighting.

bodyAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 400 / 17px / 24px / 0

Problem solving over political fighting.

body-italicAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 400 / 17px / 24px / 0

Problem solving over political fighting.

labelAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 500 / 12px / 16px / 0.04em

Problem solving over political fighting

captionAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 500 / 14px / 24px / 0

Problem solving over political fighting.

eyebrowAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 500 / 14px / 24px / 0.04em

Problem solving over political fighting

condensedAileron, 'Arial Black', 'Helvetica Neue', Arial, sans-serif 900 / 48px / 1 / 0Declared in the tokens; no Text style renders it.
body-largeAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 400 / 20px / 27px / 0Declared in the tokens; no Text style renders it.
body-mediumAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 400 / 18px / 27px / 0Declared in the tokens; no Text style renders it.
strongAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 700 / 17px / 24px / 0Declared in the tokens; no Text style renders it.
smallAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 400 / 14px / 20px / 0Declared in the tokens; no Text style renders it.
fine-printAsap, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif 400 / 12px / 16px / 0Declared in the tokens; no Text style renders it.

Color earns its place.

Contrast is the WCAG 2.2 ratio of the token against the page color and against ink. Text needs AA (4.5:1), large text and controls AA large (3:1). A token below AA is never used for text on that ground.

Palette colors

TokenValueOn pageOn ink
ink--color-ink#00294215.04:1 AAA1.00:1 below AA
page--color-page#FFFFFF1.00:1 below AA15.04:1 AAA
primary--color-primary#3384984.29:1 AA large3.51:1 AA large
primary-deep--color-primary-deep#2A6D7F5.85:1 AA2.57:1 below AA
primary-hover--color-primary-hover#004D619.40:1 AAA1.60:1 below AA
accent--color-accent#F54E4E3.45:1 AA large4.36:1 AA large
accent-deep--color-accent-deep#CC2E2E5.25:1 AA2.86:1 below AA
accent-hover--color-accent-hover#B30B0B7.07:1 AAA2.13:1 below AA
highlight--color-highlight#5AD0D61.84:1 below AA8.19:1 AAA

Surfaces surface

TokenValueOn pageOn ink
card--surface-card#FFFFFF1.00:1 below AA15.04:1 AAA
subtle--surface-subtle#F5F6F71.08:1 below AA13.90:1 AAA
tint--surface-tint#E1FBFD1.08:1 below AA13.91:1 AAA
tint-blue--surface-tint-blue#D6F1FE1.17:1 below AA12.80:1 AAA
tint-cream--surface-tint-cream#FCEFD41.14:1 below AA13.21:1 AAA
tint-lilac--surface-tint-lilac#EBDAFC1.32:1 below AA11.44:1 AAA
inverse--surface-inverse#00294215.04:1 AAA1.00:1 below AA
panel-seafoam--surface-panel-seafoamrgba(45, 202, 140, 0.27)2.11:1 below AA7.12:1 AAA
panel-goldenrod--surface-panel-goldenrodrgba(255, 194, 71, 0.27)1.61:1 below AA9.36:1 AAA
panel-orchid--surface-panel-orchidrgba(186, 116, 255, 0.27)2.99:1 below AA5.04:1 AA
panel-skyblue--surface-panel-skybluergba(50, 205, 255, 0.27)1.86:1 below AA8.10:1 AAA

States state

TokenValueOn pageOn ink
good--state-good#14704A6.09:1 AA2.47:1 below AA
warn--state-warn#8A62105.48:1 AA2.75:1 below AA
bad--state-bad#B30B0B7.07:1 AAA2.13:1 below AA
good-bg--state-good-bg#D5F0E01.21:1 below AA12.44:1 AAA
warn-bg--state-warn-bg#FBF3E41.10:1 below AA13.64:1 AAA
bad-bg--state-bad-bg#FFDEDE1.25:1 below AA12.00:1 AAA
info--state-info#00294215.04:1 AAA1.00:1 below AA
info-bg--state-info-bg#D6F1FE1.17:1 below AA12.80:1 AAA
info-mark--state-info-mark#0089DB3.75:1 AA large4.01:1 AA large

Borders border

TokenValueOn pageOn ink
default--border-default#D3D4D51.48:1 below AA10.14:1 AAA
subtle--border-subtle#E4E4E41.27:1 below AA11.83:1 AAA
ink-faint--border-ink-faintrgba(0, 41, 66, 0.08)15.04:1 AAA1.00:1 below AA
ink-soft--border-ink-softrgba(0, 41, 66, 0.12)15.04:1 AAA1.00:1 below AA
page-faint--border-page-faintrgba(255, 255, 255, 0.1)1.00:1 below AA15.04:1 AAA
page-strong--border-page-strongrgba(255, 255, 255, 0.9)1.00:1 below AA15.04:1 AAA

A 4px grid. The rhythm does not change with the device.

0--spacing-00
1--spacing-14px
2--spacing-28px
3--spacing-312px
4--spacing-416px
5--spacing-524px
6--spacing-632px
7--spacing-740px
8--spacing-848px
9--spacing-964px
10--spacing-1080px
11--spacing-1196px
12--spacing-12128px

Containers

  • prose768px
  • form896px
  • page1128px
  • wide1440px
  • full1440px
  • fluid92vw

Breakpoints

  • xs479px
  • sm767px
  • md991px
  • lg1128px
  • xl1440px
  • 2xl1440px
  • phone479px
  • phone-landscape767px
  • tablet991px
  • column1128px
  • wide1440px

Rounding is restrained.

none0
sm4px
md6.4px
menu8px
lg8px
composer16px
full9999px
xl12px
highlight10px
panel16px

Depth comes from hierarchy, not from shadow.

nonenone
surfacenone
composer0 2px 20px -6px rgba(41, 41, 41, 0.27)
overlay0 8px 20px -11px rgba(0, 0, 0, 0.2)
button0 2px 20px -6px rgba(41, 41, 41, 0.27)

Things move because something changed.

TimingDurationUse
instant0msChanges that should be invisible: the focus ring, a hover colour.
brisk150msA direct response to input: a menu opening, a switch moving.
measured250msA standard transition: a dialog opening, a sheet sliding in.
deliberate400msThe one pause after a decision that cannot be undone, before the next state.

Named patterns in the tokens

border-cycle

1.8s, ease; keyframes at 0%, 40%, 75%, 100%. Pauses under reduced motion.

Actions with deliberate emphasis.

primary
secondary
danger
text

Clear labels. Recoverable guidance.

Use the name people already say.
This appears on every screen for the assembly.
Choose a name people will recognize.
Read only: it comes from the invite.
Disabled while the assembly is archived.

Say the rule before it breaks.

At least 12 characters.
Use at least 12 characters.
Disabled while the form submits.

Structure before elevation.

Informational card

A clear content boundary groups related information without decorative elevation.

Navigational card

An interactive card lifts on hover and takes focus; a persistent text link names the destination.

Action card

A visible control makes the available next step explicit.

The main content stays distinct from its heading and its action.

Problem solving over political fighting.
Unify America

Inputs + Selection

Choose clearly.

Keep it brief.0 / 240
Choose the assembly the volunteer joins.
Selection
Visibility

Feedback + Status

State is explicit and recoverable.

Notice: info, success, warning; with an action; dismissible

This invite was already used. Sign in instead.

Signed out everywhere.

That email and password do not match.

This invite was already used. Sign in instead.

Invite sent to jane.doe@example.org. It expires in seven days.

Alert

Helpful information

Use this space to say what changed and what to do next.

Invite sent

It expires in seven days.

Review before continuing

This changes the assembly for every volunteer in it.

Dismissible

A notice the person can close.

Status, progress, loading

DraftInvitedActiveNeeds reviewExpired

Empty state: actionable, not actionable, filtered to nothing, page size

No people yet

People appear here when you invite them or when they sign up through a form.

No events yet

Events appear here once a staff member schedules the first one.

Nothing matches these filters

Clear the search or a filter to see everyone.

No problems yet

Problems and ideas appear here as the assembly raises them.

Overlays + Actions

Context appears without losing place.

ConfirmDialog: default, danger, busy

Tooltip

Small explanations, close to their cause.

Send an inviteOnly staff can see this.Changes are saved when you press Save.Keyboard shortcut: ? for help

Avatar

Identity remains present without becoming the interface.

Sizes
JSSLJD
Group
JSSLJD
JSJane Staffstaff

Content + Data

Operational information remains legible.

DataTable with FilterBar: sortable, selectable, filtered, paginated

1 selected
People, 4
RoleAssemblyStatusActions
Jane DoeVolunteer12 SpringfieldInvitedSep 14
Sam LeeStaffActiveSep 2
Maria AlvarezVolunteer14 PeoriaActiveSep 9
Devon OkaforVolunteer12 SpringfieldExpiredAug 30

DataTable: loading, empty, the standard density

People, loading
RoleAssembly
People, none yet
RoleAssembly

No people yet

People appear here when you invite them.

DataTable with eight columns: scrolls inside its own container, the key column stays

People, every column
RoleAssemblyEmailPhoneStatus
Jane DoeVolunteer12 Springfieldjane.doe@example.org217 555 0142Sep 14Invited3
Sam LeeStaffsam.lee@unifyamerica.org312 555 0199Sep 2Active11
Maria AlvarezVolunteer14 Peoriamaria.alvarez@example.org309 555 0107Sep 9Active6
Assemblies, the standard row height
AssemblyVolunteers
12 Springfield8
14 Peoria5

The plain table, search, list and metrics

Search + list

Metrics

Assemblies2+1 this quarter
Invites accepted84%+7%

Of the invites sent this month.

NameRoleAssembly
Jane Doevolunteer12
Sam Leestaff
12 Springfield
84%
14 Peoria
61%
Unassigned
12%

Application Structures

A quiet frame that gets out of the work's way.

PageHeader: league line and action; with breadcrumbs and meta; read only

Illinois Democracy League

People

Everyone in the league, staff and volunteers.

Jane DoeInvited

Volunteer in Assembly 12 (Springfield).

Page and Stack: the content column, its gutters and the rhythm every screen inherits

Illinois Democracy League

Home

Every assembly you run, the people in the league, and your own account.

First section, 24px below the header.
Second section, 24px below the first.

Home / Staff, with the league column

Democracy League OS
Jane StaffStaff

Illinois Democracy League

People

Everyone in the league, staff and volunteers.

AssemblyVolunteersNext event
12 Springfield8Sep 20
14 Peoria5Sep 27

Settings / General

Democracy League OS
Jane StaffStaff

Settings

Your account and how the league reaches you.

League details

Preferences

Dashboard / Overview (gallery only; the shell uses PageHeader)

League overview

The current work, held in one place.

Assemblies2
Decisions this week7
Recent work stays visible without turning the page into a feed.

Component / Composer

A quiet place to begin.

Assembly
empty
Scope
typing
Scope
ready
Scope
focused
Scope
disabled
Scope

Roles

The role is a word in the top bar, never a colour on the page.

RoleBadge: medium and small, both roles
StaffVolunteerStaffVolunteer
Where the role decides what renders
A volunteer on a staff routeA plain 403 page with one Home button

Staff shell / Phone

Every screen is designed at 390 first.

The same routes in a 390px viewport: the top bar holds the brand and one account control, tables scroll inside their container with the key column in place, the page keeps 16px gutters and 24px between sections, and every control is at least 44px tall.

Sign in
Home for staff
Home for a volunteer
Invite acceptance
Invite acceptance, expired link

Staff shell / Module screens

Every route the shell renders, with its example states.