A dashboard built for a screen across a room: rows of tiles as the whole navigation surface, a focus ring loud enough to find from the far side of the sofa, type sized for ten feet, a dark ground, and arrow keys that move through the board the way you would expect a remote control to. This is a shell archetype rather than a visual register. The register a shell wears is swappable; what this repo actually packages is the board and the way you drive it. The exemplars are every set-top and console dashboard of the last fifteen years, plus the smart-TV and media-centre interfaces that copied them, and none of them are imitated here: the register is a generic room-scale dashboard, and every named product is cited in prose and nowhere else.
This is not terminal-ui-theme. That is a register, a dark phosphor surface any ordinary page can wear. This is a shell: a structure a site lives inside, which happens to ship wearing a dark room-scale register and could wear a light one, as CUSTOMIZE.md demonstrates in a captured image. If you want your page to look like a machine, go there. If you want your site to be a board you drive with four arrows, you are in the right repo.
This repo is the fourth of four. All four ship the same anatomy, and they differ in the machine and in how you drive it.
- desktop-os-theme is the class's pilot, and the structure of record this repo was built against: a window pile you can cascade, minimize and raise, on a desktop designed at 1280. It is the mouse-and-keyboard answer where this one is the remote-control answer.
- retro-desktop-theme is that pilot at the other temperature, the same window mechanics wearing 1995. The pair of them is the collection's clearest demonstration that a shell and the register it wears are separate layers.
- phone-launcher-theme is the other pointerless shell. It is built for a thumb at arm's length where this one is built for a hand across a room, and it sheds the window stack entirely to show one app at a time.
The class-decision log for all four lives in the pilot: docs/class-decisions.md. This repo filed items 21 to 29 there, plus the determinism bisection and the 26-cell render matrix that the file uses as its worked example.
The screenshot above is demo/index.html, a fictional product called Muntin that puts a body of work on the screen already in a room: a studio panel, a gallery monitor, a lobby display. That is the honesty test for a shell demo. Muntin's whole argument is that a wall of tiles is a good way to meet a body of work, so its site had better be willing to be one; a site arranged as a board is describing the product rather than wearing a costume.
Open it live, or clone the repo and open the file. There is no build step, no framework, no node_modules, and no server to start.
The demo declares no colour of its own. It links tokens/tokens.css, components/components.css, shell/shell.css and shell/shell.js and reads every value from the first of those, so it stays honest about what the theme actually produces.
There is a second page worth opening: components/index.html renders the five in-view components with the markup to copy, on an ordinary page, which is its own point: the components do not need the shell.
Every theme in this collection has to be good at one thing that is hard. For this one it is the keyboard, because a dashboard designed to be driven without a pointer that cannot actually be driven without a pointer is a picture of a product rather than a product.
There are two ways to move over the board, and the second one is complete on its own.
Arrow keys move focus spatially. Not by index and not by row membership: by where the tiles actually are, measured from live bounding boxes at the moment the key is pressed. Left and right look along the current line; up and down find the nearest line in that direction and then the tile nearest in x. Nothing wraps. Running into an edge leaves focus exactly where it was, which is the truthful answer to "there is nothing over there" and the one a reader across a room can feel. Because the model is pure geometry it needs to know nothing about the layout it is steering, which is what lets the same function drive the wide board and the narrow two-column grid without a second code path.
Tab still walks every tile, in document order, exactly as it would if the script had never run. There is no roving tabindex here, and that is a deliberate departure from the usual grid-widget advice. Roving tabindex buys a short tab cycle by removing everything but one tile from it; this shell would rather pay a long tab cycle than hand a keyboard-only reader a navigation model they have to discover first. Arrows are a faster path over a surface that is already complete without them.
The tiles are not the only way through. The board ends with an index: nine plain text links to the same nine screens, under a heading that says All screens, identical whether or not the script runs. A tile is a picture a reader has to recognise as navigation before it works, and the reader who does not recognise it needs an ordinary list of links rather than a better-drawn tile. This is WCAG 2.4.5 Multiple Ways on purpose rather than by luck, and it is class decision 44. It is set at the body size in full ink, not as small print, because a text index in a ten-foot register that cannot be read from ten feet is a checkbox rather than a path. The items carry data-tv-tile like everything else, so the arrows walk them too, which is the geometry claim above tested one more time: not a line of code knows they are not pictures.
The rest of the keyboard model:
- The focus ring is the most visible object on the board, and that is a measured claim rather than a stylistic one. It is five pixels of accent at 10.39:1 against the ground, sitting on a tile whose resting state is 1.16:1 off that same ground. Nothing else on the browse surface is allowed to look like it.
- Enter opens the tile you are on. Esc backs out of the view and returns focus to the tile that opened it, every time.
- Opening a view covers the board completely, so the board leaves the tab order with it. Tab inside a view walks the view. Nothing is trapped: the back control and Escape both work, and the ARIA says
role="dialog"and deliberately does not sayaria-modal. - The hint bar names the controls in plain words and is on screen at every moment, including while a view is open, because the true sentence is different in the two states and it changes to match. It names keys and actions; it never draws a lettered button in a circle.
prefers-reduced-motionkeeps the row scroll and drops the focus scale, and the asymmetry is the point: the scroll is what keeps focus on screen, and the growth is decoration the ring can do without.tokens/tokens.cssargues it at the token.
Both walks are recorded rather than asserted; the transcripts are in the pull request that introduced them.
1280 is the native state: rows that run off the right edge and scroll, because a row that continues is what tells a reader there is more. Under 768px the board becomes a stack of two-column grids and the page scrolls down them.
Two columns rather than one, and the reason is the feature above. With one tile per line the left and right arrow keys stop meaning anything, and the differentiator quietly becomes half a differentiator on the device most people will open the demo on. Two columns keeps all four directions live. Both states are render-verified, and every one of the nine tiles is reachable by arrow key at both widths; so is every item in the index, which wraps to four lines at 390 and is walked in all four directions there.
A visual style is a set of coordinates, not a mood. This theme sits at one point in the creative direction framework, which sets brand direction on four axes. Here is where it lands and what each choice pays for.
| Axis | Position | What the position buys |
|---|---|---|
| Tone register | Conversational | A screen in a room is furniture, and furniture that shouts is exhausting. The copy is dry and direct and occasionally funny, which is a register you can live with at the edge of your vision. |
| Aesthetic philosophy | Polished Standard | Rows of tiles on a big screen is the most-copied navigation pattern in the living room. Nobody needs it explained, and that is exactly what frees the design to spend everything it has on one ring. |
| Audience relationship | Peer | The board goes unexplained past one line at the bottom. The reader has driven a screen with four arrows before, and the interface assumes it. |
| Sensory ambition | Considered | One accent, spent almost entirely on one mark. Two things move. The craft is visible mostly in what the board declines to do with a whole dark screen. |
Those four position names are the exact strings the framework uses. If you want the long version of any of them, the links go to the position page.
One of those four strains, and it is worth naming rather than hiding. A dark room-scale field with light cast across a wall behind it and a glowing ring on top of it is atmosphere, and a fair reading pushes this toward Resonant. That reading is live in this class rather than theoretical: retro-desktop-theme moved to Resonant on the same axis for the same kind of reason. It stays at Considered here because the atmosphere is one gradient and one line drawing, both optional. The scene is a data attribute the script sets; without JavaScript, and under prefers-contrast: more, the ground is a flat field and the page is unharmed. Reduced motion takes the one flourish away. What is left is a board that is dark because a bright field ten feet away in a dim room is a lamp pointed at the reader, which is a functional reason wearing a mood.
Clone once, then pick the path that matches what you came for. A shell theme has two different things worth taking, so the two grabs are stated separately.
git clone --depth 1 https://github.com/rampstackco/game-console-ui-themeGrab the shell. Four files: the tokens, the in-view components, the board, the behaviour. Your sections go in tv-view articles, your navigation in tv-tile anchors inside tv-row sections; the demo is the reference markup.
<link rel="stylesheet" href="/styles/tokens/tokens.css" />
<link rel="stylesheet" href="/styles/components/components.css" />
<link rel="stylesheet" href="/styles/shell/shell.css" />
<script src="/styles/shell/shell.js" defer></script>Grab the register only. Skip the shell/ directory entirely and you have a dark, distance-legible theme for ordinary pages: tokens plus five components, no board anywhere. This is the grab where this repo behaves exactly like the register themes in the collection.
<link rel="stylesheet" href="/styles/tokens/tokens.css" />
<link rel="stylesheet" href="/styles/components/components.css" />Tailwind v4. One import. theme.css pulls in tokens.css and maps it onto Tailwind's theme namespaces, so you get bg-tv-ground, shadow-tv-raised, rounded-tv-tile, h-tv-hint. The shell files are plain CSS and JS either way; the adapter covers the tokens.
@import "tailwindcss";
@import "./styles/tokens/theme.css";Tailwind v3. Load the tokens in your stylesheet, then register the preset.
@import "./styles/tokens/tokens.css";
@tailwind base;
@tailwind components;
@tailwind utilities;// tailwind.config.js
module.exports = {
presets: [require("./styles/tokens/preset.js")],
content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
};The demo is a plain document that a script upgrades, not an app with a fallback. With JavaScript disabled it reads top to bottom: a masthead, three sections of tile links whose rows wrap so nothing hides off an edge that cannot scroll, the text index under them unchanged, all nine sections as titled articles in reading order, and a footer. This is verified by loading the page with scripts off at both widths, not assumed. The mechanism is one class: shell.js puts tv-live on the root element as its first act, and every dashboard behaviour in shell.css is scoped under it.
The back control does not exist in the markup at all; the script injects it, because a control that does nothing must not exist. The same rule runs through the copy: the hint bar in the source says "Tab steps through the whole board, Enter opens one", which is true with no script because both belong to the browser. The sentence about arrow keys and Escape is written by the script that makes them work.
shell.js is one file of vanilla JavaScript with zero dependencies, annotated section by section, small enough to read over coffee. It is the whole shell.
This class of interface has no live search vocabulary, and the repo is named accordingly rather than optimistically. The measurement, from this collection's own keyword work: game console ui returns 10 US searches a month, console ui design returns zero, and 10 foot ui (the industry's own term for exactly this) has never had a row at all. The terms that do carry volume carry the wrong intent, which is worse than no volume: game ui design at 350 is a game-development SERP owned by Game UI Database and r/gamedev, switch ui at 150 is toggle-component documentation plus console news, and playstation ui at 70 is system-update coverage.
So the name says what the thing is to a person, the demo says what it does in five seconds, and the README leads with a picture, because that is how interfaces like this one have always actually spread. Nothing in this repo is written for a query that does not exist.
tokens/tokens.css is the single source of truth. Every literal value in the theme appears there exactly once; theme.css and preset.js hold no values of their own and point back at it with var(). The file is annotated by framework axis, every contrast ratio in the comments is computed from the hex values beside it, and the shell geometry (tile size, rail, hint bar height, the layer map) is a token group like any other, which is what keeps a re-skin a one-file edit.
shell/shell.css and shell/shell.js draw the board from those tokens. The whole shell is deliberately small: rows, tiles, a ring, views, a hint bar, and nothing else. No carousel autoplay, no parallax, no tile previews that start playing at you. Every one of those deletions is where dashboards for room-scale screens historically go wrong, and the last one is where they go from wrong to hostile.
The ground is a slot, not a colour, which is the mechanism the shell class's pilot established as a wallpaper system. Two variants ship: the flat field (a soft pool of light under the top of the board, drawn by plain CSS, which is what the no-JS document and prefers-contrast: more both get) and the panes scene, an original line drawing of light falling through a muntin-divided window onto a wall, with a picture rail, a hung frame and a plant, drawn as inline SVG so it reads the tokens like everything else. A hint-bar button switches them; the switch is one data-ground attribute on the body, all styling attribute-scoped in shell.css, and the script only toggles the attribute. The scene's paint set is restricted by rule to the darker half of the palette, so the worst text-over-scene pairing measures 11.79:1 and no variant needs a scrim; the numbers are in the shell.css comment.
CUSTOMIZE.md documents retheming as axis moves rather than as a colour picker. Its first worked move inverts the whole shell to a light register in eleven measured tokens, which on a theme this committed to being dark is the strongest available proof that the shell and the register are separate layers. It ships with a captured image, including the one thing the move breaks.
Consuming this from a Claude skill. The design-standards skill asks for a project's design tokens as a required input and offers to define a working set when none exist. Point it at tokens/tokens.css instead. The file covers every category the skill asks for, in the order it asks, and the contrast ratios are already in the comments.
MIT. See LICENSE. Use it commercially, fork it, rename the tokens, ship it. No attribution required.
Issues and pull requests are welcome here. For questions, ideas, and anything conversational, use the discussions on the claude-skills repo, which is where all discussion for these repos lives.
