Brand and design system

The Bulwark logos and the rules for using them, followed by the system this website is built with: principles, colour, type, layout, components and writing.

Six things make a page recognisably Bulwark.

The test for any new page: take the logo away and ask whether it could still only be Bulwark.

  1. One flat field. The nav and the hero share a single raspberry field, the screenshot leaves it at the right edge of the page, and the field returns once for install.
  2. One family at regular weight. Hanken Grotesk 400 for headings and body. Hierarchy comes from size and position. 500 is for buttons and table headers, 600 for the wordmark.
  3. Square, with shared edges. Tiles meet on 1px rules with the title top-left and the arrow bottom-right. Radius is 0, and 2px on the things you press or type into.
  4. The product is the picture. Real screenshots in a 1px frame, cropped where that helps, in the theme the visitor is using.
  5. Colour is an area. Raspberry is the field and a few small marks: primary buttons, icons, links, the edge of a note.
  6. Plain sentences. A heading says what the thing is or does, with at most one paragraph under it and no label above it.

Three habits bring the generic look back.

  1. A heavier headline. Weight above 500, tracking tighter than -0.02em, a size above 72, or an accent on one word.
  2. A label. An eyebrow above a heading, a caption above a screenshot, a step numeral used as decoration, or anything in uppercase monospace.
  3. Softening. A radius above 4px, a shadow under a screenshot, a gradient inside the field, or a dark section between two light ones.

Colour is assigned by role.

Components read tokens, never hex values. The grounds are neutral grey, so the only colour on a page is the brand itself.

Tokens

Fourteen roles as CSS custom properties. Dark changes the grounds, and Bulwark Lite changes brand, field and link.

RoleTokenLightDarkLite, lightLite, darkUsed for
Page--bw-page#ffffff#131315#ffffff#131315Page ground
Surface--bw-surface#f4f4f5#1f1f22#f4f4f5#1f1f22Quiet bands, footer, code blocks, notes, tile hover
Text--bw-text#18181b#f2f2f3#18181b#f2f2f3Headings and body
Muted text--bw-text-muted#56565d#ababb2#56565d#ababb2Supporting paragraphs, captions
Rule--bw-rule#dddde1#36363b#dddde1#36363bTile edges, table rows, image frames
Control border--bw-control#7f7f87#85858d#7f7f87#85858dInputs, outlined buttons, the edition switch
Brand--bw-brand#db2d54#db2d54#0f8578#0f8578Primary buttons, icons, the mark, note and active edges
Field--bw-field#db2d54#c4264b#0f8578#0c7267Nav, hero, install section
On field--bw-on-field#ffffff#ffffff#ffffff#ffffffText and buttons on the field
Link--bw-link#c01f46#ff91a8#0a6b60#6fd3c5Text links and the focus ring
Ink--bw-ink#18181b#18181b#18181b#18181bCode and button hover on the field
Error--bw-error#b42318#ff9b8f#b42318#ff9b8fValidation, with an icon and words
Warning--bw-warning#8a5d00#e2b341#8a5d00#e2b341The warning note edge
Success--bw-success#17784a#4cc38a#17784a#4cc38aThe copied state

Contrast

WCAG 2.1 ratios for every pair the pages rely on. Text needs 4.5:1, borders and focus rings 3:1.

PairMinimumLightDarkLite, lightLite, dark
Text on page4.5:117.7:116.6:117.7:116.6:1
Text on surface4.5:116.1:114.7:116.1:114.7:1
Muted on page4.5:17.3:18.1:17.3:18.1:1
Muted on surface4.5:16.6:17.2:16.6:17.2:1
Link on page4.5:16.0:18.7:16.4:110.4:1
Link on surface4.5:15.4:17.7:15.8:19.2:1
Button text on brand4.5:14.65:14.65:14.52:14.52:1
Text on the field4.5:14.65:15.6:14.52:15.8:1
Ink text on the white field button4.5:117.7:117.7:117.7:117.7:1
Code on ink4.5:115.8:115.8:115.8:115.8:1
Control border on page3:14.0:15.1:14.0:15.1:1
Focus ring on page3:16.0:18.7:16.4:110.4:1
Error text on page4.5:16.6:19.1:16.6:19.1:1

White on raspberry and white on teal are the tight pairs. Text on the field is therefore never smaller than 17px, never lighter than 400 and never muted, and raspberry itself is never used for text on a grey ground. Raspberry is close to an error red, so errors and warnings have their own colours and always carry an icon and words.

Hanken Grotesk sets everything except code.

Both families are open source and self-hosted through next/font, so the site makes no third-party requests. Sizes are desktop / phone.

Display60 / 36 px · 400 · 1.06 · -0.015em
Webmail for Stalwart
Heading 240 / 28 px · 400 · 1.12 · -0.015em
Two editions from the same code.
Heading 3, tile title21 / 19 px · 400 · 1.2 · -0.015em
Bulwark Legacy Proxy
Lead19 / 17 px · 400 · 1.45
Bulwark puts mail, calendar, contacts and files in one browser client.
Body17 / 16 px · 400 · 1.55
The setup wizard finds your Stalwart server and sets the admin password.
Small, tile text15 px · 400 · 1.5
Several address books, groups, and vCard import and export.
Button, label, table header15 px · 500 · 1.2
Read the docs
Caption, footer, breadcrumb13.5 px · 400 · 1.5
Docs / Deployment
Code13.5 px · 400 · 1.65 · JetBrains Mono
docker run -d -p 3000:3000

Pages sit on a 12-column grid with an 8px unit.

Spacing

An 8px unit with a 4px half step. Section padding is 120 on desktop, 96 on a tablet and 64 on a phone.

4
8
12
16
24
32
48
64
80
120

Grid

12 columns inside 1120px with 32px gaps. The page gutter is 80px, 40px on a tablet and 20px on a phone. Splits are 5 + 7, 4 + 8, 7 + 5 and 6 + 6, and they stack below 900px.

Screenshots

From public/screenshots, in a 1px frame, cropped with object-position. They bleed off the field or sit flush in a row. Nothing is drawn, generated or staged, and nothing sits above them as a caption.

The landing page hero and its three feature rows show the pattern. Open the landing page.

Everything is square, flat and still.

Border and radius

1px rules in the rule colour, 1px control borders, a 3px edge on notes. Radius is 0, and 2px on buttons, inputs, inline code and the edition switch. There are no shadows, gradients or blurs.

1px rule1px control3px note edgeradius 2px

Focus ring

2px solid in the link colour, offset 2px. On the field the ring is white.

Focused buttonFocused link
Focused on the field

Motion

Colour and border changes take 120ms with ease-out. Nothing moves, fades in or scrolls into place, and with reduced motion the changes are instant.

Hover me Hover me

Icons

Tabler Icons on a 24px grid with a 1.5px stroke, square caps and mitred joins. Sizes are 16, 20 and 24px, in the text colour or the brand colour, never inside a filled circle.

The pages are built from a small set of components.

They are live here: the theme toggle and the edition switch in the nav change them too.

Buttons

Primary is a brand fill, secondary an outline. On the field both turn white. One arrow, on the right, when the button goes somewhere. Every control in the nav is 36px high.

Read the docs View source Get started
Read the docs View source

Links

Inline links are underlined in the link colour. A standalone link carries an arrow. Visited links keep their colour.

Every release attaches the Lite zip, and the editions table lists the differences.

Calendar features

Edition switch

A two-part segmented control. The active side is filled with the text colour, and on the field it is filled white. The install section uses the wide form with full labels.

Edition
Edition
Edition

Tiles

Tiles share their edges. Title top-left, one line of text, arrow bottom-right, an optional brand-coloured icon above the title. Hover fills the tile with the surface colour. The list form has no minimum height.

Fact row and steps

Facts are four unboxed columns: a brand icon, a title, one line. Numbered steps are used only for a real sequence, such as an install.

Open source

AGPL-3.0, developed in public.

Built on JMAP

The client fetches only the difference.

27 languages

Translated by the people who use it.

Built in the EU

Runs on your hardware.

  1. 1
    Run the container.
  2. 2
    Open http://localhost:3000 and follow the setup wizard.

Tables

A strong rule under the header, light rules between rows, no vertical rules and no zebra. Wide tables scroll inside their own container.

KeyDefaultMeaning
appNameBulwark WebmailName in the tab, on the login page and in the manifest
jmapServerUrlemptyYour mail server

Code

Blocks sit on the surface colour with a copy button. On the field they are ink. Strings take the link colour and comments the muted colour. Monospace appears nowhere except code.

$ docker run -d -p 3000:3000 ghcr.io/bulwarkmail/webmail:latest
$ docker run -d -p 3000:3000 ghcr.io/bulwarkmail/webmail:latest

Inline: set jmapServerUrl in config.json.

Notes

One shape, three kinds, told apart by their first words and the colour of the edge.

Not in Lite. OAuth, settings sync and plugins need the Node.js server, so Lite keeps them switched off.
Note. The base path is written into every asset URL, so choose it before the build.
Warning. The build deletes the server-only folders in place. Run it in a disposable checkout.

Search dialog

Opens over the docs with the slash key. Results are grouped by section, the active row carries the surface colour and a brand edge, and the match is marked.

Form fields

Label above, help below, a 1px control border and the focus ring. An error thickens the border, adds an icon and says how to fix it.

The JMAP endpoint of your Stalwart server.

Enter a full address, for example sam@example.com.

Made in the EU

The one place the site shows a flag: once, at the right end of the footer's legal line. The flag keeps its own colours (#039 and #fc0, drawn like Wikimedia's Flag_of_Europe.svg) in both themes and editions; the words beside it take the legal line's type: no box, 13px, muted. Never in the fact row, never recoloured.

Bulwark Lite is the same page in teal.

Use the switch to see this page as Lite.

Edition
  1. Four tokens change. Brand, field, link and focus turn teal. Every neutral, the layout, the type and the radius stay as they are.
  2. The mark keeps its geometry. It takes the brand token on a page ground and white on the field, so one asset serves Bulwark, Lite and the field. Lite has its own favicon.
  3. Copy is written per edition. Where the facts differ, both versions are rendered and the edition shows one. Pages that exist in one edition say so in a note.

Headings say what the thing is or does.

Sentence case throughout, and the facts come first.

  • One positive sentence. “Webmail for Stalwart Mail Server.” “Run Bulwark as one container.” “Files sit on the mail server, next to your mail.”
  • One paragraph at most. A section is a heading, up to one paragraph, and up to one link or button.
  • Two labels a page at most. A label is small text above a heading. Most pages have none, and the docs have the breadcrumb.
  • Questions belong to the FAQ. Headings elsewhere are statements, and they say something about Bulwark rather than about another product.
  • Controls say what happens. “Copy the command”, then “Copied”. An error says what went wrong and how to fix it.

Checks before a page ships

  • Count the labels above headings. Two at most, and none is better.
  • Read every heading aloud and rewrite any that sets something up in order to knock it down.
  • Measure every radius: 0, or 2px on a control.
  • Check light and dark, Bulwark and Lite, then a 390px phone.
  • Recheck the contrast table when a colour changes.