- Client
- BallotBrief (our own product)
- Role
- Product, Research, Design & Build
- Timeline
- Built for the 3 November 2026 election
- Year
- 2026
- Category
- Civic Web Guide
Overview
BallotBrief is our own nonpartisan study guide to the Denver, Colorado ballot for the 3 November 2026 general election. It covers all 21 ballot questions (7 state amendments, 7 state propositions and 7 Denver and district questions) and 30 races, including 23 judges in 6 retention courts.
Each question opens with one plain sentence and two equal cards for what YES and NO would do. Everything else is one tap deeper: the exact change to the law, the cost, each side’s best case, who is funding each side, how four AI models read it, and what we could not verify. It is a static site with no accounts, no analytics and no server; the worksheet is saved on the voter’s own device. It was built to serve that election from an unlisted address, Denver only.
Why
A ballot is the one form almost everyone fills in, and the hardest to read. Denver’s November 2026 ballot had 21 questions on top of the races and judges, and the honest answer to “what does this one do?” was buried in a legal booklet, while the loudest answers came from the campaigns.
We wanted a guide that explains without steering: the shortest true version first, both sides with equal weight, every claim traceable, and nothing about the voter sent anywhere.
The challenge
A Denver voter in November 2026 faced 21 ballot questions and 30 races, including 23 judges up for retention. The official booklet is long and legal, campaign mail argues one side, and a guide that tells you how to vote is a different thing from one that explains. There was no quick, neutral way to see what a YES or a NO would actually do.
What made it hard
- 21 questions and 30 races, including 23 judges, on one Denver ballot, and an official booklet written in legal language.
- Campaign mail and most guides argue one side. A voter who wants both sides has to find them separately.
- Ballot titles describe a change but rarely show it. Seeing what the law says today and what it would say needs the statute and the booklet side by side.
- Money and endorsements are scattered across Denver campaign filings, news reports of state campaign money and endorsement lists.
- A guide that leans, even in its colours or the order of its answers, loses the trust it needs.
- People want to plan their votes without handing a website their choices.
Approach
Neutral by construction, not by promise. YES and NO share one card style, one colour and one length budget, and a test fails if their explanations drift apart in length. Candidates appear in ballot order. There are no party colours, no recommendations and no ballot-wide slates that one side has and the other lacks.
Every claim about a ballot question must cite a source with a URL, and items labelled reported must cite a news or court source, both checked by tests. AI is a second reader, not the author: four open models read only the official text, then checked our summaries, and where they disagree the page says so.
Process
- Gather the sources: Official ballot text, the state Blue Book and Denver’s notices, fiscal notes, Denver campaign filings and news reports of state campaign money: 142 sources, cited 307 times across the measures and races.
- A schema that refuses guesses: One JSON shape for every measure and race. Measure claims carry source ids, and an integrity check fails the gate on an unsourced measure claim, an unknown source or a source without a URL.
- Design the first screen: One plain question, then YES and NO as two identical cards with a Mark button inside each. A ballot map shows what is left, and a Next link follows the order of the paper ballot.
- The law, before and after: Python scripts pulled the changed passages out of the official booklets and classified added and removed words from the underline and strike lines drawn on the page, then each passage was checked by eye against the printed booklet.
- Second readers: Four open-weight models ran locally through Ollama, one at a time, read every question and checked our summaries. Every model read and lean-scored all 21 questions, with no failed runs.
- Review and ship: Separate reviews for neutrality, accuracy and accessibility, with their fixes applied and the key ones pinned by tests, then a static deploy to Vercel behind noindex.
What we built
- One question, two equal answers: Each measure opens with its short title, one plain-language question and two identical cards: what a YES vote does and what a NO vote does. Mark YES, Mark NO, Not sure yet and a private note sit right under them.
- Go deeper, both sides together: Rows for what exactly changes, the fine print, cost and who is affected, each side’s best case, funding, exact ballot wording, all sources and what we could not verify. Wherever a row has two sides, both open together.
- The law, before and after: For the questions that reword the law, three views: today’s law, changes marked (underlined words added, struck words removed) and the law if it passes. Sections that are new or replaced in full say so instead of inventing old wording.
- Who’s behind it: 44 campaign committees and groups across the 21 questions, with the money reported, named endorsers and each side’s first argument, laid out the same way for YES and NO. Search, filter by jurisdiction, and see groups active on more than one question.
- How 4 AIs read it: gpt-oss 20B, Gemma 4 31B, Qwen 3.8 27B and GLM 4.7 Flash read only the official text, wrote their own YES and NO, and checked ours. Their catches are shown on the page.
- Left–right lean, as an estimate: A short second pass asks each model where the policy itself sits from more left to more right. Each model is a dot, disagreements are stated, and the page says these are AI estimates, never our own placement.
- Candidates and judges: Races list candidates in ballot order as equal cards, with one toggle for everyone’s details. Each retention court has its own page and a Show details for all switch.
- My ballot: A ballot card in the order the paper ballot prints it: offices, judges, then questions. District races collapse to one find-your-district line, open items are listed with links, and it prints in two columns or copies as text. It is saved on this device only.
- Built for every screen: A floating ballot map on wide screens and a Ballot map sheet on phones, light and dark themes, a skip link, focus moved to the page on every route change, a focus-trapped map sheet and keyboard-ready tabs.
How it was built
BallotBrief is a static React and TypeScript app built with Vite. All content lives in JSON files that follow one schema, and every claim carries the ids of its sources. A Vitest suite (49 tests) checks that each measure claim traces to a listed source with a URL, that items labelled reported cite a news or court source, that YES and NO explanations stay comparable in length, and that the ballot card follows the order of the paper ballot.
The law, before and after view comes from Python scripts that pulled the changed passages out of the official booklets and read additions and removals from the lines drawn on the page, then each passage was checked against the printed booklet. Four open-weight models ran locally through Ollama to read each question and check our summaries, so no paid API was used and nothing left the machine. The site deploys to Vercel as static files with noindex.
Stack
- React 18 + TypeScript: The guide, as a static single-page app
- Vite: Build, with the data snapshot synced before every build
- Vitest: 49 tests: sources, neutrality guards, ballot order, law markup
- Python: Booklet extraction for the law, before and after
- Ollama + 4 open-weight models: Local AI cross-checks, no paid API, nothing leaves the machine
- localStorage: The private worksheet and ballot card
- Vercel: Static hosting, noindex
Outcomes
- 21: Ballot questions, plus 30 races including 23 judges
- 142: Sources behind 307 citations; an unsourced measure claim fails a test
- 4 × 21: Open models, each reading all 21 questions, with no failed runs
- 0: Accounts, trackers or servers: the worksheet stays on the device
Wins
- Every question in one plain sentence, with YES and NO in two identical cards.
- Each measure claim links to one of 142 sources, enforced by tests rather than by care alone.
- The exact change to the law shown as underline and strike, checked against the printed booklet.
- Four AI models as second readers, with their disagreements shown instead of averaged away.
- A printable ballot card that never leaves the voter’s device.
Hard parts
- Neutral in the details: Bias hides in colours, order and length. YES and NO share one style, candidates follow ballot order, and a test fails if one side’s explanation grows much longer than the other’s.
- Reading a booklet’s strike lines: The accessible PDFs’ hidden change markers were unreliable, so additions and removals were read from the underline and strike lines drawn on the page, then checked passage by passage.
- AI without letting it decide: The models read only official text and wrote their own summaries before checking ours. Lean is a separate, labelled estimate per model, never a single score and never our own.
- Honest about gaps: Where no committee was found for a side, the page says so, and every measure ends with what we could not verify instead of a guess.
Services
- Product Design
- Editorial & Information Design
- Research & Fact-Checking
- Front-End Build
- Local AI Cross-Checks
- Accessibility