Reporting redesign
- Team
- Analytics
- Owner
- Ana Ruiz
- Status
- draft
Thirty-six shadcn-style components in an ASCII skin. Frames, fills and shadows are strings of characters. Plain HTML underneath, bad signal on top.
For designers and developers who copy a component and own the code.
0 dependencies. 36 components, 34 in the kit. 2 kit files.
Nothing lives at . The link is old, or it lost a letter on the way.
The characters are paint. Underneath are native controls with labels, focus states and keyboard support, and decorative glyphs are hidden from assistive tech. Reduced motion turns off every animation and every sound.
No. The kit is two files, ascii-ui.css and ascii-ui.js. The components are classes and custom properties, so they drop into anything.
The grid is the design. Every width is a number of characters and every height a number of rows, which is what lets strings work as borders.
Yes. The Glitch switch kills it, the slider scales it, and the page respects reduced motion.
Fede Kotek designed it. Claude Code built it, in rounds: agents audit, agents fix, and a script decides what ships. The whole story is in How it was made, at the end of this page.
A design system with one material: the character.
Most component kits look like the same kit. ASCII interfaces look different, and most are screenshots or terminal toys: pure text breaks screen readers and does not reflow on a phone. The brief was a kit made of characters that still works like a kit, with a keyboard, a screen reader, a phone, and copy and paste. The person in charge is a product designer, so every rule had to be one a script can check.
Counted from the repo and the built site. A script recounts them before every release and fails if this page says otherwise.
The bar is the weight against its cap, 6 kB a #. The seven modes: keyboard, screen reader names, reduced motion, forced colors, more contrast, print, touch.
Each rule has a script, and each script is a step in the release bar. A rule no script checks is a suggestion.
| Rule | Script |
|---|---|
| No em dashes | facts.py |
| Cyan is focus only | facts.py |
| Columns on the 1ch grid | breakpoints.py |
| Reduced motion means none | reduced.py |
| No setInterval | clock.py |
| Nothing from elsewhere | budget.py |
| No tap target under 40px | audit.py |
| These numbers | facts.py |
Em dashes are looked for in the source, the docs, the built site and the commit log. Cyan is read from every CSS rule that paints with it: each one needs a focus state. Taps are drawn at 44px, and the script fails anything under 40.
One HTML file for nine versions. v10 made it a repo, with docs and Playwright scripts for QA.
v1 HTML on a character grid v2 frames are strings v3 the ring spins v4 glitch, signal palette v5 never published v7 18 more parts, Code tabs v8 6 presets, a ramp editor v10 a repo, docs, QA scripts v11 the kit: 2 files, MIT
Each row is one step heavier on the ramp. v5 is a space because it never shipped.
From v10.8 on, the work ran in rounds. Auditors read the site in parallel, one lens each, and change nothing. Their findings are split by file, so no two builders touch the same one.
audit read only, in parallel plan fixes split by file build one worktree each review reviewer and ux-critic release every step passes ship main, Vercel, live
v10.8 8 auditors, 5 builders. v11.0 10 auditors, 5 builders. v11.1 7 auditors, 4 builders. v11.2 10 auditors, 9 builders.
One # is one agent. v11.1 had seven lenses: a UI designer, legibility, copy, a frontend engineer, an interaction bug hunt, the Code tab's truth, and an LLM reading the docs cold. v11.2 went through a UX checklist: print, high contrast, touch, security, share cards, and four new overlays.
The release bar is one command. It runs 22 steps and stops at the first failure.
clean at 2 sizes, 2 themes the shipped file loads clean the Code tab prints the kit 11 widths, 360 to 1920 one animation clock no tiny taps, no tiny text no surprise phone keyboard every kit part, pasted twice every Usage tab holds true reduced motion, both builds every address lands the docs match the code what ships is what builds every number on this page under the size budget release: ok
Designed by Fede Kotek. Built with Claude Code. This is v11.5, with kit 1.3.1.
36 components, grouped by what they do. 34 are in the kit, Command and Picture are site only. Open Code on any of them and take the HTML, the CSS and the JS. New here? Get the kit, then read the rules.
What every component is made of: 14 color tokens, one grid, a ramp of nine steps (space to @), eight tones and six states. Presets in Themes swap the colors. The rest stays put.
Custom properties on :root. Dark is Signal, light is Paper. The swatches show the values, not the current theme.
| Token | Role | Dark | Light |
|---|---|---|---|
| --bg | Surface. The page. | #0a0612 | #ecebe4 |
| --ink | Text, frames, slabs. | #f3eef7 | #111110 |
| --muted | Captions, disabled. | #a79db5 | #5c5b55 |
| --hot | Acts. Primary, title bars, and the worst state (Down). | #ff3d9a | #c91468 |
| --pink | Second emphasis. | #ffb3d9 | #d02a78 |
| --cy | Focus. Nothing else. | #35e6f0 | #0a7287 |
| --ok | Confirms. On, done. | #c8f02c | #3f6b00 |
| --warn | Warns, in yellow. Errors, degraded. | #ffd23f | #8a5a00 |
| --deep | Shadows, slab edges. | #4a3dff | #2b2bd1 |
| --violet | Dividers, rules, info. | #9b7bea | #6a45d9 |
| --t0 | Title, top band. | #f3eef7 | #111110 |
| --t1 | Title, second band. | #ffb3d9 | #111110 |
| --t2 | Title, third band. | #ff3d9a | #c91468 |
| --t3 | Title, bottom band. | #9b7bea | #2b2bd1 |
One cell is 1ch wide and one row tall. Every width is a number of cells and every height a number of rows.
Weight is density. The ramp is nine steps, space to @, and the index is how loud a character is. The ramp editor in Themes swaps the characters, not the order.
| Character | Index | For |
|---|---|---|
| 0 space | Nothing. Air. | |
| 1 dot | The quietest mark. Tracks, dots, empty cells. | |
| 2 colon | Shadows, wires, the sides of a light rim. | |
| 3 equals | The light rim. Buttons and fields at rest. | |
| 4 plus | Fills on the way up. | |
| 5 star | Fills, chart marks. | |
| 6 hash | Walls. Cards. | |
| 7 percent | Dense. A primary button under the pointer. | |
| 8 at | The loudest. Primary, focus, nodes. |
A frame is one character repeated. The tone class names which one. Five tones use ramp characters. Faint, danger and error use -, / and !, which are not on the ramp.
| tone- | Character | For |
|---|---|---|
| heavy | at. Primary buttons, focus. | |
| dense | percent. A primary button under the pointer. | |
| mid | hash. Cards, and any button under the pointer. | |
| light | equals. Buttons and fields at rest. | |
| shade | colon. Shadows under cards. | |
| faint | -. Empty states, disabled. | |
| danger | /. Destructive actions. | |
| error | !. A field that is wrong. |
What each form component draws in each state. A dash means it has no such state. Blue is only ever focus.
| Component | Default | Hover | Focus | Active | Disabled | Error |
|---|---|---|---|---|---|---|
| Button | rim by tone | denser rim | @ rim, blue | ink slab | faint, gray | none |
| Input | = rim | none | @ rim, blue | none | faint, gray | ! rim, yellow |
| Select | = rim | none | @ rim, blue | none | faint, gray | ! rim, yellow |
| Textarea | = rim | none | @ rim, blue | none | faint, gray | ! rim, yellow |
| Toggles | [ ], on is lime @ | none | blue slab | none | gray | none |
| Toggle group | on is an ink slab | none | blue slab | none | gray, struck | none |
| Slider | halftone bar | none | blue label | none | none | none |
| Input OTP | [_] | none | blue cell | none | none | yellow [ ] |
The rim says how loud it is: @ for primary, = for the rest, / for danger. The last one is disabled: a faint rim and a gray label.
Focus turns the frame to @ in the focus color. An error turns it into a wall of !.
Use lowercase letters, numbers and hyphens.
Checkbox, radio and switch. Native inputs, the glyph is only the paint.
Pick a value on a range. A real range input sits on top of the halftone bar. This one sets the page's glitch.
The active tab is a solid slab. Arrow keys move between tabs.
<button class="btn btn-primary
frame tone-heavy">
<span class="mid">
<span class="label">
Publish site
</span>
</span>
</button>
.tone-heavy {
--h: "@@@@@@@@@@@@...";
--s: "@@";
}
.frame::before,
.frame::after {
content: var(--h);
overflow: hidden;
}
.mid::before,
.mid::after {
content: var(--s);
}
Title on a bar of @, walls of #, and a shadow made of colons. Publishing opens the dialog.
How far along a task is, on the halftone bar. The spinner lives inside the button.
A native details element. The marker flips between [+] and [-] and the answer decodes as it opens.
Thirty-four components in two files, one CSS and one JS. No build step. Link them once, then copy any component.
Yes. Every frame reads its characters from two custom properties, so a new tone is two strings.
Inverse slabs for status, brackets for everything else.
A card with a hazard rim. The title says what happened, the body says what to do.
Probes from 3 regions are timing out. Check the load balancer in fra-1.
Maintenance window on Sunday at 03:00 UTC. Probes keep running.
A native select inside the same frame as the input.
Loading is a wave moving through the ramp.
Initials on a slab, or a picture pushed through the LCD.
Slashes do the separating. The current page is a slab.
A month of buttons. Today is the action color, your pick is a slab.
The command menu. On this site it is Search: press / and type. Site only, not in the kit.
A button that opens a menu. Arrows move, Escape closes.
When there is nothing, say so and offer the next step.
Nothing is broken. Enjoy it, it never lasts.
Six brackets. They advance on their own and take a paste.
Type any six digits.
Shortcuts, as keys in brackets. These ones work on this page.
Thirty-nine icons made of the same characters as the text. Each comes in two sizes: one row, to sit in a line of text, and three rows, for an empty state. Tap one to copy it.
Export finished. 214 rows, none of them lost.
The share link stops working in 2 days.
No results for "invoice 2019".
Check the spelling, or search all time.
39 icons.
Copies <span class="icon" data-icon="search" role="img" aria-label="Search"></span>
Brackets around every page, a slab under the current one.
An LCD panel cut into twelve sectors. Every cell is three subpixels and each sector can run its own mode. Tap a sector. Site only, not in the kit.
Splits content without a box. Four weights of nothing, and a rule with a word in it.
A dialog that comes up from the bottom in seven steps.
Five ways to wait.
The same frame, four rows tall, with a counter.
0/280
Events in order. Nodes are @, the wire is colons.
A real table that sorts, filters, picks rows and pages. Take the script away and it is still a table.
| INC-512 | api-gateway | Down | 2026-09-27 22:29 | 3 | us-east |
| INC-511 | webhooks | Degraded | 2026-09-27 21:37 | 48 | ap-south |
| INC-510 | cdn-edge | Degraded | 2026-09-27 20:02 | 7 | sa-east |
| INC-509 | scheduler | Resolved | 2026-09-27 17:22 | 21 | eu-west |
| INC-508 | webhooks | Resolved | 2026-09-27 16:45 | 24 | us-east |
| INC-507 | cdn-edge | Down | 2026-09-27 16:08 | 94 | ap-south |
| INC-506 | scheduler | Resolved | 2026-09-27 12:45 | 36 | sa-east |
| INC-505 | search | Resolved | 2026-09-27 10:37 | 6 | eu-west |
| INC-504 | cdn-edge | Resolved | 2026-09-27 10:00 | 55 | us-east |
| INC-503 | scheduler | Degraded | 2026-09-27 08:49 | 9 | ap-south |
| INC-502 | search | Resolved | 2026-09-27 06:41 | 58 | sa-east |
| INC-501 | billing | Resolved | 2026-09-27 06:04 | 31 | eu-west |
| INC-500 | scheduler | Resolved | 2026-09-27 03:56 | 118 | us-east |
| INC-499 | search | Resolved | 2026-09-27 03:04 | 4 | ap-south |
| INC-498 | billing | Degraded | 2026-09-27 02:27 | 12 | sa-east |
| INC-497 | notifications | Resolved | 2026-09-27 01:50 | 62 | eu-west |
| INC-496 | search | Resolved | 2026-09-27 00:15 | 15 | us-east |
| INC-495 | billing | Resolved | 2026-09-26 22:40 | 83 | ap-south |
| INC-494 | notifications | Resolved | 2026-09-26 22:03 | 42 | sa-east |
| INC-493 | checkout | Resolved | 2026-09-26 21:11 | 140 | eu-west |
| INC-492 | billing | Resolved | 2026-09-26 20:34 | 11 | us-east |
| INC-491 | notifications | Resolved | 2026-09-26 18:26 | 18 | ap-south |
| INC-490 | checkout | Resolved | 2026-09-26 16:51 | 75 | sa-east |
| INC-489 | auth | Resolved | 2026-09-26 16:14 | 27 | eu-west |
One line, typed in from the left. Lime for good news, yellow for the other kind.
Pick one of a few options. Radios dressed as slabs. Arrow keys move the pick, Tab leaves the group.
Board view.
A short hint for a control, on hover, focus or tap. It types itself in.
A small panel next to its button, for things that do not deserve a dialog. Escape or a click outside puts it away.
Type and the list gets shorter. For when a Select would scroll forever.
A dialog that asks before something you cannot undo. A tap outside does not count as an answer.
The menu under the right mouse button. Press and hold on a phone, Shift F10 on a keyboard.
| ID | Service | Status |
|---|---|---|
| INC-481 | api-gateway | Down |
| INC-480 | checkout | Degraded |
| INC-479 | search | Resolved |
Right-click a row, or press and hold it.
Components put together into the pieces every product ends up needing. The content is from Static, a made-up uptime monitor that screams when things break. The six under Personal are an example portfolio. It is Fede's, who made this. Swap in your own work.
Two fields, one loud button, one quiet one. Submit it empty to see it complain.
The number is a bitmap, the trend is eighteen characters of ramp.
Uptime, last 90 days
99.98 percent.up 0.02
p95 latency, milliseconds
182 milliseconds.up 14
Incidents this month
2 incidents.down 3
A real table. Tap a row to select it.
| ID | Service | Status | Time |
|---|---|---|---|
| INC-481 | api-gateway | Down12m | 12m |
| INC-480 | checkout | Degraded48m | 48m |
| INC-479 | search | Resolved6m | 6m |
| INC-478 | webhooks | Resolved1h 02m | 1h 02m |
| INC-477 | auth | Resolved3m | 3m |
No incident selected.
Three cards. The popular one gets the @ rim.
$0 forever
$29 per month
$199 per month
An about block. The portrait is an LCD panel, so load your own face.
Fede Kotek designs B2B products in Buenos Aires. A decade of it: fintech, marketplaces and industrial software.
Interaction, motion and visual craft. Thinks in systems, would rather not run the design system.
A timeline with opinions.
Four cards, four LCD thumbnails. Tap one.
Dashboards people read without being asked to.
One box, every incident, under 200 ms.
If this breaks, then page someone. No code.
Messages that stay with the incident.
Pick a case study to read its one line.
What is going on outside of work.
Sunday asado. Change the servings and the quantities follow, in metric.
A character sheet on the halftone bar. Reroll it.
A checklist that counts. It is release day, because it always is.
App navigation with counts. The arrow follows the current page.
These two switches are wired to the real page.
A feedback form with a verdict.
A feed is a timeline with timestamps.
The one page where the signal is lost for real. It never settles.
404.Signal lost. This page went for a walk and did not leave a note.
Charts drawn on the character grid. Density carries the value, color carries the meaning. Tap them.
Requests per day. Bars grow through the ramp and cap out lighter.
Tap a bar.
p95 latency, live. Spikes turn yellow. Tap the chart to break production.
p95 --
Probe success by region, one halftone bar each.
Uptime by day. Lime is fine, yellow is degraded, magenta is an outage.
Tap a day.
Traffic by source. Yes, another donut.
Tap a slice.
Six presets, six color roles, four support shades and one ramp. Change any of them and the whole site follows. Copy the tokens when you like what you see.
Pick a signal. The page is the preview.
Every color has a job: surface, text, action, focus, success, warning. The four support shades follow the preset.
Nine steps, space to @, lightest to heaviest. Every frame, fill, chart and explosion is an index into them. Swap the characters and you have a different kit.
Classic ramp.
The theme you are looking at, as CSS. Paste it after ascii-ui.css, or download tokens.css for a page. For Figma or Tokens Studio, download tokens.json: both modes, the grid, the ramp and the motion.
The bad signal, for your own pages: four effects an element asks for with data-aui-signal, then two kinds of scanlines for the whole page. Nothing runs until asked. Reduced motion, high contrast and print keep all of it still.
A state change breaks up for two frames, then sits still. Press the button.
Words decode into place once, when they come on screen. A screen reader gets the words, never the noise.
Twelve probes, three regions, one pager. Nothing is down.
A tracking band rolls through now and then. It is see-through and takes no clicks.
Channel 3. The picture holds, mostly.
The band passes. The words stay.
Leave the page alone for four seconds and the frame decays. Touch anything and it is back.
Nobody has touched this in a while.
The crt class on the root, over the whole page. Subtle by design: on a dark theme they sit mostly in the text. Interlace draws every other line, fainter. Neither moves.
Five small labs. Each one is a piece of the system you can poke.
Brightness picks the character. Drag across the patch to move the light, and use the slider to change the exposure.
A border is one long string clipped by overflow. Type any character and the frame rebuilds itself out of it.
Headlines are a 5 by 7 pixel face where every pixel is four characters. Type up to eight and watch it develop.
Four effects, fired one at a time. On the rest of the page they fire on their own when state changes.
No audio files. Square waves for blips, a sawtooth for errors and filtered noise for the glitches.