We built a voting-deadline countdown that runs on your website, not ours. Then we spent longer trying to break it than building it — and broke it twice, including once with a mistake we had made ourselves two days earlier.
Here is the thing itself. It is live, it is running the real dates, and it is the same file anybody can paste onto their own site:
Two lines of HTML:
Change NE to your own state, or leave the attribute off entirely and your visitors get a state picker. There is a builder page that writes the snippet for you and shows a live preview while you fiddle with the options.
This site has a lot of pages about money and votes. But the failure that actually costs somebody their vote is smaller and dumber than any of that: they meant to register, and then it was November.
A countdown is the one piece of this whole project that works better on somebody else's page than on ours. The people who most need the date are not browsing a civic transparency site. They are reading a church bulletin, a union local's announcements, a school board blog, a neighborhood Facebook page someone mirrors to WordPress. So the countdown should go there.
A folder called DawnWidget has been in this repo since August. It is genuinely good code — Shadow DOM, multiple instances per page, no style leakage. I reused its best idea.
It also cannot go live, and the reason matters: it takes a data-api attribute pointing at a Flask backend that is not running anywhere public. That is the exact same root cause that took our ZIP-code portal down on September 3rd — a good front end wired to a server nobody is running.
We could have shipped it anyway with a note saying “requires an API.” It would have looked like a release. It would have worked for zero people.
So this one carries its own data. Every date for all 51 states is baked into the file. Once the script loads, it never talks to a server again — not to ours, not to anyone's. That is 22 KB, which is smaller than most photographs on most websites.
First working version, Nebraska. The big number said:
That date is real and correctly sourced. It is also not a deadline. Nothing happens to you if you ignore it — it is the county telling itself when the ballots exist. Meanwhile the date that actually costs you your vote if you miss it, register by October 16, was buried in small text underneath.
A countdown that leads with an administrative milestone is technically accurate and practically useless. The fix was to sort dates into two kinds — things you can miss, and things that merely happen — and let only the first kind lead. Nebraska now opens with “Mail your registration (postmark) — Oct 16.”
Worth saying: North Dakota and Vermont correctly lead with Election Day, because neither state has a registration deadline you can miss. North Dakota does not register voters at all; Vermont lets you register right up through Election Day. The rule produced the right answer for the states that break the rule, which is the only way you find out a rule is any good.
Texas showed this, twice, with the same label:
| Row | Label | Date |
|---|---|---|
| 1 | Early in-person voting opens | Oct 17, 2026 |
| 2 | Early in-person voting opens | Oct 19, 2026 |
That is our own mess from two days ago. On September 4th we found that our data file said October 17 — the statutory seventeenth day before the election — while the Texas Secretary of State's own page publishes Monday, October 19 as the first day of early voting. Rather than silently overwrite one, we kept both in the file with a note explaining the difference. That was the right call for a data file.
It was the wrong call for a widget, because a widget has no room for a footnote. A visitor does not see our careful note. They see the same sentence twice with two different days and conclude, reasonably, that we do not know what we are talking about.
The widget now shows the date the state actually publishes to voters, and the file keeps both with the explanation. Same discipline, different surface.
If you paste something onto your church's website and it turns the page into a ransom note, you will never paste anything from us again. So before release we loaded the widget on a page and then injected deliberately hostile styling — the kind of aggressive global rules a badly-built theme really does ship:
The host page went lime green, exactly as instructed. Inside the widget:
| Property | Host page | Inside the widget |
|---|---|---|
| Background | rgb(0, 255, 0) | rgb(23, 48, 79) — ours, unchanged |
| Font | Comic Sans MS | ui-sans-serif — ours, unchanged |
| Font size | 40px | 26px — ours, unchanged |
| Border | 6px dotted red | 2px solid gold — ours, unchanged |
Nothing crossed the boundary in either direction. That is what the Shadow DOM is for, and it is the part of DawnWidget that was right all along.
A widget that sits on other people's websites is a surveillance opportunity. Ours does not take it. No cookies, no analytics, no beacon, no network request of any kind after the script file itself downloads. We cannot see who embedded it, how many people saw it, or who clicked.
You do not have to believe that. The file is right here, it is not minified, and it is about a hundred lines of logic under a big block of dates. Read it. That is the whole point of this project.
Free, forever, no attribution required, no permission needed. Put it on a campaign site, a church page, a company intranet. We are not going to ask what you did with it.
The widget counts down to dates. Some states publish rules instead — “register any day up to and including Election Day,” or “40 days before Election Day.” A countdown cannot count down to a sentence, so twelve states currently show one date or fewer: Alaska, Alabama, Arizona, DC, Idaho, Maine, Montana, North Dakota, New Hampshire, Ohio, Utah and Vermont.
For some of those — North Dakota, Vermont — that is simply the truth about the state. For others it is our data gap showing. We are not going to guess which, and the widget links to the full page where the actual sentence is printed out.
The other thing this does not survive is time. Every date here is 2026. On November 4th this widget becomes a monument. Making the data outlive one midterm is now the top item on the list, because the whole point of building it this way was that somebody could still use it in 2028, and 2030, and after that.