@oval-vote/brand
v0.1.1
Published
The Oval design system: tokens, components, logo, and wordmark
Readme
Oval Brand
The design system for Oval, the plain-text format for voting guides, and for
the organization that publishes it. Everything here is the source: the mark, the wordmark, the
colors, the typefaces, the tokens, and the components that the website, the renderer, the editor,
and the extension draw on. index.html shows all of it on one page and is published at
brand.oval.vote.
The mark
The mark is a ballot oval, filled in: a printed ring, a gap of paper, and the voter's ink inside. The ellipse is 1.75 times as wide as it is tall. The ring is the marking purple and 0.075 of the height thick; the fill is ink, 0.65 of the height wide and 0.325 tall, centered. The gap between them is what makes it read as a marked ballot rather than a dot.
| File | Use |
| --------------------------- | ---------------------------------------------------------------------------------- |
| logo/mark.svg | The mark in color on light paper: ink fill, purple ring. |
| logo/mark-reverse.svg | The mark in the dark palette, for dark paper. |
| logo/mark-mono.svg | One color, taken from currentColor, for places that allow a single color. |
| logo/mark-outline.svg | Both ellipses as strokes in currentColor, for an unfilled state. |
| logo/wordmark.svg | "Oval" set in Instrument Serif as outlines, in ink. |
| logo/wordmark-reverse.svg | The wordmark in the dark palette. |
| logo/lockup.svg | The mark beside the wordmark, centered on the capitals. |
| logo/lockup-reverse.svg | The lockup in the dark palette. |
| logo/favicon.svg | A 32-pixel version with a thicker ring. |
| logo/icon.svg | A square app icon: the mark on paper with rounded corners, 1024 pixels. |
| logo/avatar.svg, .png | The square avatar for GitHub and other services that round the corners themselves. |
Every file in logo/ is generated by npm run logo from one geometry in scripts/logo.mjs, which
downloads Instrument Serif from Google's fonts repository and outlines the word with fontkit, so
the files contain no text and need no font installed. npm run avatar rasterizes the avatar. CI
fails when either is stale.
On dark paper use the -reverse files. A page that follows the viewer's theme shows one or the
other with a <picture> element and a prefers-color-scheme media query, or two images toggled
by the same theme rules as the tokens, as index.html does.
Keep clear space of half the mark's height on every side. Do not show the mark smaller than 16 pixels tall (use the favicon below that), stretch it off its ratio, rotate it, add a shadow or gradient, place it on a pattern or photograph, or recolor it, least of all in party colors. Do not place it beside a candidate or party as if it were their bubble. The wordmark is always Instrument Serif; do not set the name in another face or add a tagline inside the lockup.
Color
The palette is a ballot: paper, ink, and the purple that marks it.
| Token | Light | Dark | Role |
| -------------- | --------- | --------- | -------------------------------------------------------------------- |
| paper | #f2f1ec | #131313 | Page background: warm stock in light, neutral graphite in dark. |
| paper-raised | #fbfaf7 | #1c1c1c | A surface lifted from the page: a card, a callout, a control. |
| paper-sunken | #e9e8e2 | #0d0d0d | A surface set into the page: code blocks, wells. |
| ink | #17150f | #e8e6e1 | Text, the mark's fill, anything drawn in ink. |
| ink-soft | #5f5b50 | #9a9892 | Secondary text: eligibility lines, metadata, captions, placeholders. |
| rule | #cfccc2 | #2c2c2c | Hairlines, borders, table rules. |
| mark | #8347ae | #b48ad9 | The marking purple: the ring, link underlines, focus rings, badges. |
| mark-soft | #ebe1f4 | #2a2234 | A tint of purple for badge and hover backgrounds. |
| support | #2e6b3a | #6cc07a | A supported campaign or a yes recommendation. |
| support-soft | #e3efe4 | #1a2a1d | A tint of support for badge backgrounds. |
| oppose | #c4343a | #ef6f72 | An opposed campaign or a no recommendation. |
| oppose-soft | #f8e3e3 | #331c1d | A tint of oppose for badge backgrounds. |
Red and blue carry party meaning in American politics, so the accent is purple, which belongs to
no party, and the only red is the one that means "against" on a specific question. Support is a
green that is not a party color either. Never use mark to color a party, a candidate, or a
position on a question; parties are labeled with their own abbreviations in ink.
Both themes are designed, not inverted. The purple is a different value per theme so that it
passes as text on both papers: 5.3:1 on light and 6.7:1 on dark. Every text color reaches 4.5:1
on its paper; index.html tabulates the ratios.
Type
| Role | Family | Weights | Use | | ------- | ---------------------------------------------------------------------- | -------- | -------------------------------------------------------- | | Display | Instrument Serif | 400 | The wordmark and display-size headings only. | | Text | Newsreader | 400, 500 | Running text and headings. Variable, optical sizes 6–72. | | UI | Public Sans | 400, 600 | Labels, controls, badges, navigation, tables of data. | | Mono | IBM Plex Mono | 400, 500 | Oval source, identifiers, and code. |
Newsreader is a newspaper text face, which is where voting guides have lived for a century. Public
Sans is the typeface of the United States Web Design System, so civic interfaces already read in
it. Instrument Serif gives the name a voice of its own at large sizes and is not used for text. All
four are open source and served from Google Fonts; tokens/oval.css names the fallback stacks.
The scale is display 4.5rem, h1 2.75, h2 2, h3 1.5, lead 1.375, body 1.125, mono 0.9375, small 0.875, label 0.75. Running text is at a line height of 1.5 on a measure of 44rem; headings are at 1.15; uppercase labels in Public Sans take 0.06em of letter spacing.
Space and shape
Spacing is a 4-pixel base: steps 1, 2, 3, 4, 6, 8, 12, 16, and 24, named by their multiple. Radii are small (0.125rem) for badges, control (0.25rem) for buttons and fields, card (0.5rem) for surfaces, pill, and oval (50%) for the bubble. Rules are 1px hairlines. There are no shadows; surfaces are raised by tone and a rule.
Components
css/components.css defines classes prefixed oval-, built only from the tokens: type roles
(oval-display through oval-label and oval-mono), oval-link, buttons (oval-button with
-secondary, -quiet, -small), fields (oval-field, oval-input, oval-select,
oval-textarea, oval-field-note), badges (oval-badge with -support, -oppose, -mark),
oval-card, callouts (oval-callout with -support, -oppose), oval-table, oval-code, and
the ballot row (oval-ballot, oval-ballot-row, oval-bubble, oval-bubble-filled,
oval-stance). The primary button is ink on paper and turns purple on hover; purple is never the
resting color of a control. Focus is a 2px ring in the marking purple on every interactive element.
Transitions are 150ms and drop to 0ms under prefers-reduced-motion.
Tokens
tokens/oval.css declares every value above as a custom property prefixed --oval-, with the dark
values under prefers-color-scheme: dark and under [data-theme="dark"], so a page can follow the
system or set a theme itself. tokens/oval.json is the same set as data, with a description on
each token, for tools that generate from it.
Load the tokens, then the components if you want them, then the fonts:
<link rel="stylesheet" href="https://brand.oval.vote/tokens/oval.css" />
<link rel="stylesheet" href="https://brand.oval.vote/css/components.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Instrument+Serif&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400&family=Public+Sans:wght@400;600&family=IBM+Plex+Mono:wght@400;500&display=swap"
/>Voice
Oval's writing is plain and declarative, the register of a specification: short sentences, active
voice, nouns people recognize. It does not pitch. The name is "Oval" in prose, .oval as a file
extension, and oval as the command and the keyword; never "OVAL" or "oval" at the start of a
sentence about the project.
Using the identity
The logo and wordmark identify the Oval project. Use them to refer to Oval, to show that a tool reads or writes the format, or to link to oval.vote. Do not use them to suggest that the project endorses a guide, a candidate, a party, or a position, and do not modify them.
License
The files in logo/ are licensed under
CC BY-ND 4.0: copy and redistribute them
unchanged, with attribution, and do not make derivatives. Everything else, including the tokens,
the stylesheets, and the scripts, is licensed under the MIT License. Both texts are in LICENSE.
Developing
npm ci
npm run logo # regenerates every file in logo/ from scripts/logo.mjs
npm run avatar # regenerates logo/avatar.png from logo/avatar.svg
npm run check # formatting and that the generated files are current; CI runs thisOpen index.html in a browser to see the system on one page.
Publishing
Every push to main that passes npm run check deploys the page to GitHub Pages. npm run build
assembles dist/ from index.html, logo/, tokens/, and css/, and the workflow uploads that
directory, so nothing else in the repository is served.
The same files are published to npm as @oval-vote/brand, so a project can depend on the tokens
and components instead of linking to the site:
npm install @oval-vote/brandimport "@oval-vote/brand/tokens/oval.css";
import "@oval-vote/brand/css/components.css";
import tokens from "@oval-vote/brand/tokens/oval.json" with { type: "json" };A release is a tag. Set the version with npm version <version> --no-git-tag-version, commit, tag
the commit v<version>, and push the tag. The release workflow runs the checks, confirms that the
tag matches package.json, and publishes with provenance through
trusted publishing, so no token is stored anywhere.
