Every elected prosecutor in the country, searchable by zip code

Web app

https://ppp.unc.edu
The Prosecutors and Politics Project homepage scrolling inside a browser window

The Prosecutors and Politics Project is a nonpartisan research initiative at the University of North Carolina School of Law. It studies the 2,300-odd locally elected prosecutors who decide what gets charged in American criminal courts, and it cites a survey finding that most Americans do not realize the job is elected at all.

The research was already done. What it lacked was reach: findings that live in a PDF reach the people who already read PDFs. This site turns the same dataset into something a voter can interrogate from their own zip code, and hands the research team a way to keep it current without going through a developer. A university center holding a dataset and no developer is the brief I get most often.

RoleDesign and build lead
Scale2,300+ prosecutors
Runs onNext.js and Airtable

The first screen

A map colored by the finding itself

The country, shaded by the share of prosecutor elections that went uncontested, with a search box above it that takes a state, a county or a zip code. Hover a state and it gives you its own number: Missouri, 68% uncontested across 2014 to 2024. The project's central claim is that most of these races have no challenger, so the first thing on the page is that claim, drawn, and checkable against where you live.

The US map shaded by uncontested election rate, the pointer resting on Missouri, which has lit up with a View button and a tooltip reading 68% of elections uncontested from 2014 to 2024

The claim, tested

Type your zip, get your prosecutor

Three digits and five counties in Arkansas still match. Five digits and it is Orange County, North Carolina, and one click away is the person who makes charging decisions where you live. A project about 2,300 officials nobody can name has to answer the only question a visitor actually arrives with, which is who is mine.

The empty search field above the national map, placeholder reading Search by state slash county slash zip

Search by state, county or zip

Going deeper

How this state does it, then every county scored

Each state gets a written explanation of how it elects prosecutors, because the rules genuinely differ: Missouri runs partisan races in 115 jurisdictions, one for each county plus the city of St. Louis. Beside it sit the numbers that make the comparison, and under it the same shading applied county by county, so the pattern inside a state is as visible as the pattern across the country.

The client edits this themselves, in the CMS

The Missouri page explaining its prosecutor elections beside a panel of statistics, above a county map shaded by uncontested rate

Filtering

Four filters, and the address bar remembers them

Election year as multi-select toggles, county name, candidate surname, and a single checkbox for uncontested races. The state of the filters lives in the URL, so a researcher who narrows to one county across three cycles can send that exact view to a colleague or cite it in a footnote.

The filter row with election year toggles, county and candidate fields and an uncontested checkbox above the results count

The evidence

Every row carries its paperwork

Vote shares, badges for the only candidate on the ballot and for the one who won, and a documents button that opens the filings behind that row. Where there are none it says so rather than leaving a dead control. A research project asking to be taken seriously has to show its sources at the level of the individual record, not in a bibliography at the end.

A close view of result rows showing vote shares and documents buttons, with no documents stated plainly where there are none

How it stays current

A checkbox in a spreadsheet, and the site follows

The research team does not file a request to change the public record. They work in the base they already keep the data in, and a row reaches the site when its Publish to website box is ticked. One row here is ticked and live; the two under it are entered, checked and still waiting. That is the difference between a dataset somebody maintains and a dataset somebody has to ask a developer to maintain, and over a project's life it is most of the difference.

The elections base in Airtable with a Publish to website column, ticked on the published rows and empty on the 2025 records still being checked

Election to election

Every cycle since 2014, on its own page

A researcher studying the 2020 cycle lands on a page about 2020, nationally, with the same filters sitting over it. Same data as the state pages underneath, different front door, so nobody has to work out how to filter their way to it.

The 2024 election year page showing that cycle's races across all states

By affiliation

Eleven of them, including the races with no party at all

The third front door into the same table. Nonpartisan races get a page, and so do the ones where the affiliation was never recorded. Keeping the awkward categories, with no other bucket to sweep them into, is exactly what makes the data usable to somebody who cares about the difference.

The Democrat party page listing that party's prosecutor races nationally

Under the hood

The client updates it from a spreadsheet

All four datasets live in Airtable, where the team already works. A record appears on the site when its publish checkbox is ticked and disappears when it is not, so correcting the public record is an edit, not a request.

Seventy-three pages, and none of them wait

Every state, year and party is a real page, rendered before anyone asks for it and refreshed when the data changes. A dataset this size usually means a spinner. This one opens like a brochure.

Adrial is my go-to designer for anything my business needs … which is why I have used him for three different companies over the last 20+ years.

Craig WoodCEO, Definian

Your great work deserves to look great.

Twenty-five minutes. Tell me what you're trying to do and who needs to see it, and I'll tell you what I'd build first and roughly what it costs.