Changelog
What's new since 0.1.0
Everything below is in the changelog itself, entry by entry. In short:
- Your style on top. Your mood boards become styles (Design Eye), each product picks the one that
fits (Style Fit),
style usewears it andstyle setmoves one setting. The slop police stops a style that reads as made by AI. Echo's library has Drafting Index beside Echo's own. Styles - Light and drawn devices. A pale chassis makes a light device, and
render: drawndraws every part in the ink. Pills apart from the radius, a display size, and readability past AA. Theming - Checks and tests. The check in your code, the page check on the running page, Echo's tests with the design tests and axe, the Stop gate, and a job for every pull request. Checks and tests
- An agent that knows it's in Echo.
initwrites the Echo block,DESIGN.md, the skills and the hooks, andwhichanddocsanswer an agent's questions offline. For AI agents - The MCP server, hosted, and a plugin. Ten tools, on your machine or at a hosted address, and Echo's plugin for Claude and ChatGPT. MCP server, Plugin
- Upgrades that keep your edits.
diffsays what changed, andupdatemerges Echo's change into the files you edited. CLI - More components. Dates, file upload, a table that sorts, selects, filters and windows, app navigation, the choosing controls, containers, the objects (dials, keypads, readouts), the motion family and the creature. Components
Notable changes to Echo UI. The format follows Keep a Changelog, and this project follows Semantic Versioning.
What a version means for a copy-paste registry. Installed components live in your source tree, so an upgrade is never automatic — npx echo-ui-cli add overwrites a file you may have changed. Versions here describe the registry: what a fresh install of a given item gives you, and whether re-adding an item you already have can change its behaviour.
Every entry declares what it is. Above each bullet sits a line the build reads and markdown hides: <!-- bump: breaking; items: echo-toast, echo-count-badge -->.
bump is patch, minor or breaking. items is the registry names the entry changes, all for a change that genuinely sweeps the library, or none for one that touches no installable item — the CLI, the MCP server, the docs. The build refuses a changelog with an entry that declares neither, and refuses one naming an item that does not exist.
This is not ceremony. A /e/ version is published once and never rewritten, so the number an item ships under cannot be taken back — and it used to be inferred from how this file was formatted: a regex for a bolded "Breaking" run across the whole unreleased section, and attribution by "does the prose mention this name in backticks". One entry marked breaking moved every item that changed that cycle, and an entry that said "Menu and Sheet are unchanged here" filed itself as a change to Menu and Sheet. Write the prose for a reader; declare the facts for the build.
What each bump means.
patch— fixes that do not change an item's API or its rendered semantics.minor— new items, new props, new tokens. Re-adding an existing item is safe.breaking— a prop, a CSS class, a token name, or the registry namespace changed. Re-adding an item may require edits on your side; the entry will say which.
Breaking does not mean major, while Echo is 0.x. Semver leaves 0.y.z to the project, and Echo follows the common reading: a breaking change bumps the minor. So breaking and minor both produce the next minor today, and they are still declared separately — they say different things to a reader, and they will produce different numbers the day Echo reaches 1.0.
Unreleased
0.3.0 — 2026-10-12
Added
- Drafting Index, Echo's second style (Echo's library, read by
echo-ui-cli'sstyle list,style fit,style showandstyle use). Beside Echo's own, Echo publishes a style drawn in one black ink on white paper, hollow until it's on:render: "drawn", pill keys on crisp boxes (radius0.3,pills), Inter Tight for sentences and IBM Plex Mono for labels and values, with its rules and decisions for the agent.npx echo-ui-cli style use drafting-indexwears it, no board needed. It passesstyle checkand the slop police, and a test holds every published style to both and to a theme with nothing hard to read.
- The MCP server, hosted (
echo-ui-mcp). The same tools asnpx echo-ui-mcp, over HTTP athttps://mcp.echo-ui.com/mcp, for the tools that only connect to a remote server: ChatGPT, claude.ai, Lovable, v0, Bolt and Claude Design. It needs no account, reads what echo-ui.com publishes (so it answers what the CLI installs), and keeps nothing it's sent:check_codereads your code and forgets it. One address can send up to 300 requests in ten seconds. - Every tool now declares that it only reads (readOnlyHint), so a client can run it without asking each time. -check_codetakes up to 200,000 characters, and says so. A request larger than that needs is refused with413before its body is read, so no one call can take the server's memory (BUG-222).
- Echo's plugin installs from echo-ui.com before it's listed (
echo-ui-mcp's plugin build)./docs/pluginsays what the plugin adds to the hosted server (Echo's three skills, which know the workflow) and links two downloads that every deploy now carries:echo-ui-claude.zip, for claude.ai's Upload plugin andclaude --plugin-dir, andecho-ui-codex.zip, a marketplace of one forcodex plugin marketplace addin Codex and ChatGPT desktop. The ZIPs are written in Node, sopnpm build:registrywrites them on any machine, Windows included, and the same files always make the same bytes.
- Echo's plugin, for ChatGPT, Codex and Claude (
pnpm --filter echo-ui-mcp build:plugin). One source, two folders: Claude's (.claude-plugin/plugin.json,.mcp.json) and OpenAI's (plugin.jsonon the Agent Plugins schema,mcp.json, and a ZIP for its portal; with.codex-plugin/plugin.jsonand.mcp.jsonbeside them, which Codex and ChatGPT desktop read, so the same folder installs there too). Both carry the hosted server and three skills that work in a chat as well as a project: Echo UI, which routes (a project with Echo follows its own skill, one without runsinit, a chat makes the style and hands the build to the code tool), and Design Eye and Style Fit, each the project's skill plus what every step becomes without a terminal. The skillsinitwrites into a project are unchanged.
- Styles in the MCP server (
echo-ui-mcp). Four tools do whatnpx echo-ui-cli styledoes, for a builder in a chat with no terminal, from the same code, so the two give the same answers: -list_styles: Echo's styles, or one in full. -fit_styles: Echo's styles and the builder's own (passed in by their fingerprints), ranked against where the product sits on the seven axes, asstyle fitranks them. -check_style: astyle.jsonread the waystyle checkreads it, the slop police included. -wear_style: what a style gives a project. A link that makes the whole Echo site wear its colours, theinit --styleorstyle usecommand for the builder's code tool, and, for a project with no terminal, the theme CSS and aDESIGN.mdwith the style's rules. A style the police stops isn't worn: it says what to ask the builder.
A builder's own styles never live on the server: they come in with the call. A standalone DESIGN.md made from a style now opens with init --style <name>, so a project set up from it keeps the style's rules, not only its colours.
- Design tests, and the gates that run them (
echo-ui-core,echo-ui-cli). The page check now measures what a designer checks on a screen an agent built: alignment (text cut off with "…", cards side by side whose actions sit at different heights, a lone action off its column's edge, a grid that leaves columns empty), depth (a control that glows at rest, a raised surface on a raised one with no well), corners (parts of one kind with different corners, a corner inside a corner that doesn't follow it) and icons (one group's icons at different sizes, strokes or styles), beside contrast. Each finding carries what it measured and the one change that fixes it, ending with what to leave alone. Three ways to run them: -npx echo-ui-cli testruns them on the app's pages (its routes, ortest.routesin echo.json) and exits 1 on a finding, with a report and a picture of each in.echo/report/..echo/ignores itself; nothing else in the project changes. - In Claude Code, a Stop gate: when the agent tries to finish, the tests run on the pages it changed, and it keeps working until they pass (three rounds at most; it lets the agent stop when the app isn't running, a page can't be loaded, or a page shows the dev server's error overlay instead of itself, and says which).initandagent onadd it beside the check hook;"test": { "stop": false }in echo.json turns it off. -init --ciwrites a GitHub Actions job that runs them on every pull request.
check --url, test and the gate render pages in the Chrome already on the machine, over its DevTools protocol, and fall back to the project's Playwright: no project needs Playwright to run them. Echo's own gallery and every docs page pass them; on the page an agent built in the blind runs that passed every check Echo had, they find all four of its layout problems.
- axe on every page, with nothing to set up (
echo-ui-cli,echo-ui-core).test,check --url, the Stop gate and theinit --cijob now run axe-core on each page they load, after Echo's own checks: its WCAG 2.0, 2.1 and 2.2 A and AA rules, which catch what an agent writes outside Echo's parts (an image with noalt, a target under 24px,aria-hiddenover something focusable, an ARIA value that isn't one). On a page planted with fourteen common mistakes, Echo's checks found none and axe found eleven. - Each finding is in Echo's shape: the rule and its WCAG criterion, the element as Echo names it, a picture in the report, and Echo's answer where one of its parts or props is it (/settings axe button "Bold": target-size (WCAG 2.5.8, AA): this target is 14px by 14px …). A control with no name is said once, by Echo'sname. Contrast stays Echo's own. - It counts like any rule."check": { "rules": { "axe": "info" } }says without counting,"axe": "off"turns it off, and"axe:target-size": "info"does either for one rule, which is how a project keeps the problems it already has without hiding new ones. Anaxe:name axe doesn't run fails loudly. - The report lists, under each page, what only a person can check, never counted: what axe couldn't decide, each iframe (axe doesn't look inside frames), and a page axe couldn't run on. axe failing on a page never costs it Echo's own findings, and it has 10 seconds. - The Stop gate tells you whenecho.jsonlets more through than at the last commit (a rule turnedoffor toinfo, or the gate turned off), and tells the agent never to mute what it can't fix. -axe-coreis a dependency ofecho-ui-cli, pinned exactly (4.13.0) and kept as its own package (it's MPL-2.0).echo-ui-coredoesn't run axe, and<EchoCheck />is unchanged, except that an element with no words of its own (an icon key, a field) is now named by itsaria-labelin what it says:input#code "Code".
- A display size, and pills on their own (
echo-ui-core,echo-ui-cli). The type scale gains3xl(40px) and adisplaytext style (the sans, bold, tight), for the one large line a screen carries: a headline, an entry's index number. Both Paperlab moodboard runs reached for one and found the scale stopped at 26px. And a seed's newpillsmakes buttons and segments pills whatever the radius (style set pills true), so a style can have pill keys on crisp boxes, or soft boxes and square keys;radiusis the corners of boxes, panels and fields. Unset, pills come with radius 1 as before, so every seed renders as it did. Design Eye readsradiusfrom the boxes andpillsfrom the buttons; at radius 1 both runs had pill keys and 29px panels.
- The drawn device (
echo-ui-core,echo-ui-cli, every component that draws its own material). A seed's newrendersetting ismolded(Echo as it has always been: plastic lit by one key light) ordrawn: the same parts drawn in the ink, the way a spec sheet draws a key. Every part is outlined at one weight (--echo-line,--echo-line-width), hollow on the body; a raised part stands on a hard ink edge as deep as it rises and travels down it when pressed; a recess is an outline drawn inside; something floating stands on a deeper edge. There's no light, so no gloss, no soft shadow and no glow: what a molded device lights (an LED, the rail, a readout, the focus ring, a fader's knob, a radio's pip) is solid ink, and a low-emphasis action is its label underlined. In black and white (the accent the ink) "on" is solid ink and what's drawn on it inverts: a switch's thumb turns paper, a fader's knob is knocked out with a paper ring. The primary key is the paper key that stands highest, on an ink edge twice as deep as a neutral one's, the way a spec sheet draws its CTA. The accent stays the seed's own colour as a flat fill for what's on (a chosen segment, a checked box, a slider's travel, a switch that's on), so an acid highlighter is acid, not the olive a molded device holds it to; what's drawn on it is the ink, or the body when the accent is too deep for the ink. Pressing, travel, sound, what each material is for and every contrast guarantee are unchanged.npx echo-ui-cli style set render drawnturns it on; a style sets it in its seed; the published style and config schemas describe it, andDESIGN.mdsays how a drawn device builds. A molded device renders exactly as before: every line is 0px wide there, and each drawn value a component reads (--echo-drawn-fill,-on-fill,-ink,-rule,-glass,-hollow,-decoration) isinitial, so it falls back to what the component always drew. HardwareButton's bed is the one new hand-tuned shadow: a drawn key stands on its own edge instead of being drawn in a recess.
- Style Fit reads an earlier decision as questions, not rules (
echo-ui-cli's echo-style-fit skill). An earlier decision for the same product in the library is read first, for what it knows that the brief doesn't, but each of its constraints is carried as a question: what it protected, whether it still holds, a suggested answer. A constraint that holds is written as the need ("the sheet must stand off its surround"), never as one style's fix for it ("a darker frame"), and a new decision for the product is dated and says what it changes.
- Design Eye reads the board, then asks, before it writes a style (
echo-ui-cli's echo-design-eye skill). Builders don't make clean boards. Between the reading and the writing the skill now sends one message, at most three questions, each with its suggested answer: the colour, said as what the board agrees on counted in tiles ("eight of ten tiles are black ink on paper; the acid is on one"), with black and white as an answer; each tile that doesn't share the board's logic, and whether to leave it out, read it as its own style or take one thing from it; and the one reading the board supports both ways (which part is the main action). The answers go in the foundation underBUILDER'S ANSWERS. The accent is the builder's answer, never one tile's loudest swatch.
- Design Eye reads outlined, hollow parts as a setting, not a refusal (
echo-ui-cli's echo-design-eye skill and itsecho.mdreference). The skill's grammar used to say "parts are molded and lit, and caps travel when pressed" in one breath, and both its examples taught that flat or outlined means refuse, so a board drawn in line, outlined pills that still look pressable, came out as Echo's molded plastic in the board's colours. Pressing is the grammar; how a part is rendered isrender, molded or drawn. Before refusing, the skill reads its own distillation again, and if a refusal would drop the style's character it looks for the setting or asks the builder. A decision comes from a part doing that job on the board, never from a neighbouring one. The reference marks which of Echo's rules are its own look (the light, seams, no borders, the glow) and which are the grammar.checkstill catches a shadow recipe written out as a molded device draws it.
Changed
- Every item needs
echo-ui-core0.3.0 (all components). The drawn device, readability past AA and the design tests live in core, and the components that use them are built against it, so each item now declaresecho-ui-core >=0.3.0 <0.4.0. Update core with the items:npm install echo-ui-core@^0.3.0(or your package manager's), thennpx echo-ui-cli update. An item whose files didn't change is the same code under a new version; the 0.2.0 files stay published.
echo-ui-mcpneeds@modelcontextprotocol/sdk1.32.1 or later (echo-ui-mcp). 1.30 and the versions ofhono,fast-uri,ip-address,qsandproxy-addrit brought in had published security advisories; none was reachable through Echo's server, which uses none of the parts they sit in, and the hosted server runs the fixed versions from its next deploy.
- The MCP server keeps answering while echo-ui.com can't be read (
echo-ui-mcp, local and hosted). A catalog, registry index or style it has already read is trusted for five minutes; past that, a read that fails used to fail every tool. Now the copy it has keeps answering, and the source is tried again every minute until it reads.
- Every prop of Echo's own says what it does (the catalog,
llms-full.txt,get_componentandnpx echo-ui-cli docs). 74 had no description, among themfeel,sizeandalignon the morph buttons, HardwareButton'svariant,loadingandcaption,useTactile's phases and every seed field, which only the docs site described. A prop goes without one now only when its meaning is the platform's: an HTML attribute, an ARIA name, or a controlled-state pair (BUG-214).
echo.json's schema says what each check rule finds (echo-ui-cli). The schemainitpointsecho.jsonat now describes every namecheck.rulestakes, so an editor shows whatspace,restyleoralignmentis as you type it. The docs list them on a new page,/docs/echo-json, from the same text, and the CLI guide's flags table now comes from the flags the CLI parses, all 27 of them (BUG-210).
- The MCP server runs on zod 4 (
echo-ui-mcp). Its tools take and answer what they did, and every prop table they answer with reads exactly as before. What a client is shown of each tool's arguments loses one line: zod 4's converter leaves out"additionalProperties": false, so a schema no longer says that the tool takes nothing else. It never refused anything else: an argument a tool doesn't take was dropped before, and still is.
Fixed
- A release that needs a new
echo-ui-coreis a new version of every item (the registry). An item whose files hadn't changed kept its version, so its released file and its latest pointer could declare different cores, andaddcould refuse a project on the new one./e/{name}.jsonis now always the pinned file it names (BUG-234).
- Right after a release, the registry index names that release (the registry). With nothing unreleased,
/e/index.jsoncalled itself the next patch, and every released pin is now served as it was released, never rebuilt (BUG-232).
echo-ui-cliandecho-ui-mcpship their licenses (echo-ui-cli,echo-ui-mcp). Each package now carries Echo's MITLICENSEand aTHIRD-PARTY-NOTICES.mdwith the license of every package bundled into it (BUG-231).
init --ciwrites a pnpm job that runs (echo-ui-cli). Unlesspackage.jsonnamed pnpm's version, the job's first step stopped with "No pnpm version is specified" on every pull request. It now names the pnpm the project runs (BUG-230).
SoundProvidercloses its audio context when it unmounts (echo-ui-core). It opened one on the first press and never closed it, so a provider inside a page left an audio thread running for every visit, until the browser made no more. A provider near the root, as the docs advise, was never affected (BUG-228).
- In a monorepo, the Stop gate tests the page that changed (
echo-ui-cli). Git names changed files from the repository's root and Echo named pages from the app's folder, so in an app atapps/webno edit ever matched its page, and the gate tested the first ten pages of the app instead. A change outside the app's folder still counts for every page (BUG-226).
- A pinned
/e/version is cached for good again, and read in one request (the registry,echo-ui-cli,echo-ui-mcp). Cloudflare redirects{name}@{version}.jsonto its%40spelling, and the immutable cache rule only reached the redirect, so the file itself was revalidated on every read. The rule now covers the file as it's served, and the CLI and the MCP server ask for that spelling directly (BUG-225).
--jsonoutput is the result alone wheninitoraddinstalls (echo-ui-cli). The package manager printed on the CLI's stdout, so pnpm's progress and npm's "added 2 packages" came before{ "ok": true, … }and an agent couldn't parse it. What the package manager says now goes to stderr (BUG-224).
- A registry that stops answering is given up on after 30 seconds (
echo-ui-cli,echo-ui-mcp). Reads of/e/and the catalog had no time limit of their own, so a host that took the connection and never answered heldinit,addand every MCP tool for Node's five minutes, saying nothing. Now the command or the tool stops with the address that didn't answer (BUG-223).
- An
echo.jsonwith a syntax error stops the CLI instead of being written over (echo-ui-cli). One stray comma used to read as noecho.jsonat all:addsaid to runinit,initwrote a fresh file over your seed, style and rules, and the check hook and the Stop gate passed every edit without a word. Now every command stops with the file's name and what the parser said, and nothing is written; the same goes forecho-lock.json(BUG-219).
- The accessibility claim reads as a sentence. It ended "Not covered yet: screen-reader: not yet verified by a person." in the README, on the site and wherever the claims are quoted; it now ends "Still open: screen-reader, not yet verified by a person." Each item's published record of what is still open is unchanged (BUG-212).
checknames the role a colour is for when several roles are that colour (echo-ui-cli, the MCP server'scheck_code). A drawn device in one black is its text, its seam, its glow and its lit ink at once, and a hairline typed as#050505was told to usevar(--echo-accent-glow), the first of them the theme lists; on a molded device that line would glow. The property now decides: a border or outline is the seam, a text colour the text, a background the surface (BUG-204).
whichfinds Segmented for a choice "among a few" (echo-ui-cli, the MCP server'ssearch_components). "One choice among a few" and "pick one among a few paper stocks" answered nothing; only "one of a few" and "among a few options" were known (BUG-203).
- Grid, ChoiceCards and ShortcutMap no longer leave columns empty (BUG-189). Fewer items than fit share the row (
auto-fit), so two cards in a row with room for three are two wide cards, not two cards and a gap.
- A row of Cards lines up, and Item shows its whole title (BUG-188). A Card alone in a list item fills it, so
<Grid as="ul">of cards is one height a row; the footer sits at the bottom and starts at the content's edge instead of the far end (afullWidthkey spans it). Item's title and description wrap onto more lines instead of ending in "…"; the newtruncatekeeps one line each.
whichknows the jobs three blind-run agents asked for (echo-ui-cli, the MCP server'ssearch_components). Site navigation, FAQs, a monthly/annual toggle, abandoning with a confirmation, an empty screen of any kind and a level that fills like liquid name SideNav, Accordion, Segmented, ConfirmButton, EmptyState and Meter; "heading text" points at the heading and display text styles, and "an icon set" at Lucide (BUG-187).
whichknows more of the ways a second agent says a job (echo-ui-cli, the MCP server'ssearch_components). "Announce that the code was copied" and "a numeric value readout" answered nothing, "one of many options in a dropdown" answered RadioGroup and "switch between two formats" Switch. They name Toast, LcdChip, Select and Segmented now, and "a section label" says to print it in the machine-label text style (BUG-185).
whichfinds the part when it's asked by the job (echo-ui-cli, the MCP server'ssearch_components). The echo-ui skill tells an agent to ask by job, and "a setting from 0 to 100", "the one main action", "a choice of one among a few options", "a label and value table", "a row with a label and a control" and "the app's frame" answered nothing (or Badge and Table), though "a slider" or "a button" found the part. They name Slider, HardwareButton, Segmented, DescriptionList, Item and AppShell now; a possessive reads as one word; and "a block of code" says Echo has none, and what to build it from (BUG-184).
0.2.0 — 2026-10-04
Added
- At roundness 1, buttons and segments are pills (
echo-ui-core, HardwareButton, Segmented). The theme writes--echo-cap-round(1 atradius1, 0 below it), and a cap's and a segment's corners reach half their height when it's 1. Under 1 nothing moves: the corner ismax(the molding, 0), the same pixels as before. A style whose board shows pill buttons setsradius1; Design Eye's table says so. Before, the softest molding stopped at about 14px on a 48px cap, and a style that asked for pills couldn't get them.
- Readability, past AA (
echo-ui-core,echo-ui-cli). The theme's report gainsreadability: every text role on every ground it sits on, in APCA; a dark device's cap against its body and its lit rim against the cap; and lit text and the lit cap as people with protanopia, deuteranopia and tritanopia see them. What's hard to read for anyone (under APCA Lc 45) is a warning instyle check,style useandstyle set, and failsstyle check; what's under comfortable (Lc 60) is a note. The page check (<EchoCheck />,check --url) says small text that passes AA and reads under Lc 45.apcaContrast,readabilityandsimulateare exported fromecho-ui-core/theme.
- The slop police (
echo-ui-cli, the MCP server'scheck_code). Some combinations read as made by AI the moment they appear: a cream ground with a terracotta or orange accent; near-black with one acid colour; cream, brass or oxblood and a serif for anything "premium"; indigo or violet on white in a stock sans; one stock face for everything. Each ingredient is fine alone.style checknow stops a style whose own settings make one of these pairings, names the ingredients, prints the question for the builder, and exits 1;style usewon't wear it;style fitmarks it on the list. A builder keeps one on purpose with the style's newkeeps({ rule, reason }, in their words), and the agent skills say an agent never adds one.checkgains asloprule for the code an agent writes around Echo's parts: gradient text, an emoji as an icon, the sparkle for "AI", labels that don't say what happens ("Get started"), an arrow stapled to a label, a headline that could front any product, a stripe down one edge, spaced capitals made by hand,A · B · Cstrings, and stand-in names or photos, which can never be kept. A number that looks invented is said, not counted. Every finding says what to do instead. The chart is dated and credits its sources. Decisions in a style.style.jsongainsdecisions: one short line each for how something is made important, the main, second and destructive actions, "you are here", how a state is said, what each type size is for, how a screen is composed, surfaces, what each colour is for, icons, imagery, how the product talks, and an empty screen.style usecarries them and the builder'skeepsintoecho.jsonandDESIGN.md, which lists what the style leaves undecided;style checkandstyle showsay them too. Design Eye reads a board as three systems (type, buttons, surfaces and colour), treats each colour as a job with its sampled value as evidence, leaves the board's own tells out and names them, and writes the decisions. Style Fit weighs the police and the decisions. All three skills ask the builder when they aren't sure (one message, at most three questions, a suggested answer first) instead of reaching for a default, and the Echo block says so in one line. The published style and config schemas describe the new fields.
style set: one setting, one line (echo-ui-cli).npx echo-ui-cli style set radius 0.8 finish 0.7moves one setting of the project's seed, or several: the change an agent makes when it's asked for "rounder", "denser", "glossier", another accent or another typeface. The settings are the seed's own (accent,chassis,paper,lcdTint,finish,radius,densityand the four font stacks). Every value is checked before anything is written, and every problem is said at once, with the same checkstyle checkuses. The theme andDESIGN.mdare written again, contrast is held as always, a pale chassis is said to make a light device, and the output ends with the line that puts it back.defaultgives a setting back to Echo;style setalone lists them all. A project that wears a style keeps it, marked with what moved (style.changedinecho.json, which the published config schema now describes), andstyle useputs the style back whole. The Echo block names the command, and the skill maps words ("rounder", "roomier") to settings. The check hook, when an agent edits the theme file by hand, now points it atstyle set; it used to tell it to move its components out of Echo's folder (BUG-171).
- The light device (
echo-ui-core,echo-ui-cli). Echo can be light: a pale chassis makes a device molded in light plastic, like a white calculator or a cream radio, and a dark one makes the device Echo has always been. There's no setting and no mode to switch; the chassis decides (devicePolarity, andvalues.polarityon the theme). On a light device the page is light: its ground, the well, is the body barely set in. Caps get their own ladder, a step lighter than the body with a lit rim that stops short of white, so they keep their bevel; undersides and seams are shaded darker; dim text moves toward the ground by the same step as on a dark device, and reads about 6:1 on it. Screens are paper glass printed in ink, because a glow on a pale body is a smudge. What's lit (an LED, the current rail) is still the accent, held to 3:1 on every ground, so on is brighter than off; the text it prints (a readout, a count, a current tab) and the focus ring are the accent's ink, a step deeper and held to AA, in the newaccent-inkrole. The primary cap is the same cap as a dark device's, the accent with a light label: its label is the newprimary-cap-inkrole, and progress cells, loader dots and a meter's fill, the same plastic, read 3:1 on the page. A neutral cap or a slider that fills with it under its own dark label fills with the newpour-*roles, the accent's pale stops. Shadows are drawn at half their weight, in a deep colour of the body's own hue (--echo-cast-ink) instead of black. Every guarantee holds as it does on a dark device, and the contrast sweep now runs on light bodies too. A dark seed makes exactly the theme it did before, and the new roles equal the ones they stand for there. The components that mixed a recess toward black, painted paper in the text colour or lit a pillow of light with it read the theme's new--echo-shade-ink,--echo-paper-stockand--echo-paper-inkinstead, with the old value as the fallback, and a reflective screen reads--echo-screen-*; the components that print a lit label read--echo-primary-cap-inkand--echo-accent-inkthe same way; every rail is drawn in the glow, and every focus ring, theringrecipes included, in the ink.style usewears a light style now, andstyle checksays which device a style makes. A dark screen tint on a light body is still refused: one text colour can't serve both, and the theme says so.
- Echo's Style Fit (
echo-ui-cli).initandagent onwrite a third skill,.claude/skills/echo-style-fit/: ask your agent which style the product should wear, and it reads what the project says about itself (asking only what's missing), places the product on the seven axes with how far a style may drift on each, hasstyle fitorder every style in your library and Echo's against that, reads the best of them, and argues for one in a decision saved to your library; then it wears it withstyle use, and keeps what it adapted for this product inDESIGN.md.style fit --profile "W4 V2 D1 F2 E3 C3 O3" [--tolerance …]is the arithmetic (a tolerance of 0 locks an axis, and a style that misses a locked one is out), andstyle show <name>prints one style in full with where its foundation is. A style that's only its foundation document, written before Echo, is measured from the library's index, and Design Eye writes its Echo part from the document, with no moodboard.init --style <name>sets a project up wearing a style from the start, and a new Styles guide (/docs/styles) says what a style changes, what it can't, and the ways in.
- Echo's Design Eye (
echo-ui-cli).initandagent onnow write a second skill,.claude/skills/echo-design-eye/: give your agent a moodboard and ask for the product to look like it, and it reads each style on the board into a design foundation (what the style is made of, the rules underneath it, the products it fits) and astyle.jsonEcho can apply, with the board's real colours and magnified details as evidence fromstyle sample. Before it writes rules, it reads what the project is for, and asks only what's missing: who it's for, their age, what they come to do, where. It saves each style to your own library, never into the project, checks it withstyle check, and offersstyle use. It comes with one reference, Echo's own style written as a foundation, so the agent has a finished example to write to.agent offtakes it out with the rest.
- Styles:
echo-ui style list | use | check(echo-ui-cli). A style is a look a project wears on top of Echo: it changes the vocabulary (colours, type, radius, finish, density), never the grammar.style listshows Echo's own styles (the first is Echo's device,echo) and yours, from your own library (~/Documents/styles, orECHO_STYLES_DIR), private to you.style use <name>writes the style's seed toecho.jsonandecho-theme.css, says what the theme corrected for contrast and which fonts to get, and puts the style's rules inDESIGN.mdfor your agent; the style's documents stay in your library.style check <file>says whether astyle.jsonis one Echo can apply, listing every problem. Editors read the file's schema from/e/schema/style.json.initkeeps a project's style when it runs again, and a new--seedreplaces it.style sample <image> --out <folder>is the evidence for reading a style from a moodboard, with no Python: the plate, its dominant values (the real hex, dark to light, with each one's share of the pixels) as a strip, and magnified crops of the surfaces a claim rests on (--crop x,y,w,h:name,--zoom). A collage, as a moodboard exported from a canvas is, gives each tile its own plate with--region x,y,w,h. It reads PNG and JPEG. A style with a light body is worn on Echo's light device (above).
createEchoThemesays what it corrected (echo-ui-core). The result carriesreport.corrected(each role the contrast guarantee moved, and the ratio it reached),report.unsatisfiable, andreport.accentwhen the accent was moved into Echo's lightness band (an acid yellow comes out olive), so a tool can say it rather than leave it to the console.init,style useandstyle checksay all three.
- FileUpload (
echo-file-upload). A recessed tray you press to choose files, or drop them on, its inner edge lighting while files are over it; it's a real button, so the keyboard opens the file dialog. Each file becomes a row with its name, what's happening to it, a slim Progress strip while it goes up, and a key to cancel, retry or remove it. It has no upload code of its own: give itupload(file, report, signal)and it shows the progress your handler reports and the message it throws; without one, it holds the files and a form submits them undername.accept,maxSizeandmaxFilesrefuse a file in words, and what happens to each file is said aloud.whichanswers "a file upload" with it.
- Dates: DateField, Calendar and DatePicker (
echo-date-field,echo-calendar,echo-date-picker). DateField types a date into wheels, day, month and year in the order the locale writes them, each a row of small flaps that turn over to the value, in Field's well and on its size scale: type the digits and a wheel moves on by itself, or step it with the arrows. On a phone it opens the numeric keyboard, and a pasted ISO date fills every wheel. Calendar is a month on a device screen: one Tab stop, the calendar keys (arrows, Home and End, Page Up and Page Down), the chosen day lit, today on a lit rail, and days that can't be chosen struck through. DatePicker is the two together: a calendar key in the field's well (or Alt+↓ on a wheel) opens the Calendar under it, and choosing a day closes it and turns the flaps. All three takemin,maxandunavailable, write the value as an ISO date (2026-09-28) so no time zone moves a day, follow the locale's order, names, digits and first weekday (Arabic runs right to left, Hebrew left to right), and put the date in a form withname. A date outside the limits is said in words under the field.
- Popover can be opened and closed from outside (
echo-popover).openandonOpenChangemake it controlled, and a close from outside still animates out.initialFocussays what takes focus as it opens,anchorplaces it against something other than its trigger (a whole field rather than the key in it), andaria-labelnames a popup that has notitle.
whichknows the date parts, and what's coming soon (echo-ui-cli,echo-ui-mcp). "A date picker" gets DatePicker and DateField, "a booking calendar" gets Calendar, and a job that's coming soon says so: "an image slider" gets "A carousel is coming soon" rather than Slider.
- Table sorts, selects, filters and windows its rows (
echo-table). Give itcolumnsandrows(andgetRowId) and it draws the head and body itself: a sortable column's header is a button whose LED pips light the direction, witharia-sort;selectableadds a Checkbox to every row and a select-all that reads "some";filteris a query from your own search Field;sizesets the row height from the control scale; a table with its ownheightkeeps its header at the top, and withvirtualdraws only the rows in view, so two thousand rows cost a screenful.loadingandemptyrows,rowActions, andonRowPress(a real button in the label cell) round it out. Sort and selection work controlled or not. Rows that move slide to their new place, new ones fade in, and a pressed row gives; under reduced motion, none of them move. The sort, a filter's count and loading are said aloud. A table laid out by hand works as before.
DESIGN.mdfor a tool with no terminal, and the whole seed forinit(echo-ui-cli). The generator downloadsDESIGN.mdfor the seed you're looking at, for Lovable's Project knowledge or a Claude Design upload. It opens by telling an agent that has a terminal to runinitinstead, with that seed, so it never stands in for the components.init --seedtakes the whole seed as the generator shows it,accent,chassis,paper, as well as the accent alone, and refuses anything else with the form it takes./docs/aisays what works in v0, Bolt, Lovable and Claude Design.
updatemerges your edits with Echo's change (echo-ui-cli). A file you edited used to hold its whole item back forever. Now a file you edited and Echo didn't stays yours, and one you both changed is merged against the version you installed, which the registry keeps. Where your edits and Echo's change meet in the same lines, nothing is guessed: the item is left as it is and the lines are printed between git's conflict markers (and in--json). Bring them together in your file and runupdateagain: it merges once your file carries Echo's change. With no copy of the installed version to merge against, the item is left as it is, andupdatesays why.--overwritestill takes Echo's whole.
check --urlchecks a running page (echo-ui-cli). It loads the page headless with the project's own Playwright and runs the check<EchoCheck />runs in development, printing its findings the waycheckprints the source's, with the team's rules applied,--json, and exit1when it finds something. It installs nothing: a project with no Playwright is told how to add one.echo.jsoncan turn the two rules only a page has,setupandcontrast, off or toinfo.
<EchoCheck />: the running page, checked in development (echo-ui-core,echo-ui-cli). Put once in the root layout, it looks at the page whenever it changes and says in the console whatcheckcan't see in the source: a colour the app paints that the seed doesn't make (a theme colour at any opacity, or a mix of two, counts as the seed's), a control with no name, text under WCAG AA against what's really behind it (gradients sampled where the text sits), a missing theme, ground orSoundProvider, and the screen rules that need a render (two primary caps in one form, "you are here" lit twice, an invalid field with no words).overlaylists the findings in a corner panel. A production build gets a component that renders nothing, through thedevelopmentexport condition, so none of it ships.initprints it as the third setup step, and the agent guidance asks for it.
initwritesDESIGN.md, and Echo keeps what it writes for agents current (echo-ui-cli).DESIGN.mdis this project's look in Echo's terms (the colours its seed makes, type, spacing, depth, motion, sound, do's and don'ts) in the sections Google's DESIGN.md format uses, and it opens by telling an agent in the codebase to build with Echo's components rather than from the file. It's read when a screen is built, not in every session, and stays under 8,000 characters. It sits between Echo's markers, so aDESIGN.mdof your own keeps everything else. The Echo block points to it. A Cursor project whose block went toCLAUDE.mdalone also gets a Cursor rule.init --seedrecords the seed inecho.json, and every file Echo writes for agents records the CLI version (and seed) that wrote it:addandupdatebring Echo's marked files up to date,checksays when one is another version's or another seed's, andagent onwrites them again.agent offtakes it all out.
- The MCP server can check code (
echo-ui-mcp). A sixth tool,check_code, runs Echo's check on a file's code handed over as text (TSX, JSX or CSS) and answers with the same findings asnpx echo-ui-cli check, each with the Echo way to fix it, against the default theme. For an agent with no terminal to run the CLI in. The check's rules now live in one place both bundle, so the two can't disagree.
- The team's own rules, in
echo.json(echo-ui-cli). An optionalchecksection turns a ruleoffor toinfo(said, not counted), bans an import with the team's own message ("our icons are in @/icons"), and allows brand colours the seed doesn't produce. Nothing needs it; it only adds. A rule name that isn't one, or a value it doesn't take, stops the check with what's wrong, and the hook reports it as the check failing, never as the agent's mistake. The publishedecho.jsonschema describes it.
checksays when a heavy part loads with the page (echo-ui-cli). A part that draws with WebGL (ShaderField, today), or one with a lot of code of its own, imported at the top of a file holds up the first paint; the line gives the lazy import for the project's framework (next/dynamicwithssr: false, orReact.lazyin Vite). The catalog's weights now say which parts draw with WebGL.
- Every item has a fact card (
echo-ui-cli,echo-ui-mcp). What it weighs (its own code, with what it imports, and its CSS, minified and gzipped, measured when the registry is built), what it needs, which keys do what, what reduced motion does to it and the sounds it makes: "At a glance" on its docs page, aWeight:line innpx echo-ui-cli docsand the MCP server'sget_component, andweightincatalog.json. Every part you can operate now says which keys do what; sixteen that didn't (Receipt, Ticket, NowPlaying, OtpField, Tag, Banner, Pagination, Card, QuickTile, PatchBay, TagsInput, Form, SaveButton, Stopwatch, AddToBag, useTactile) gained the line.
checkknows how an Echo screen is put together (echo-ui-cli). The screen rules one file can show: another library's parts on an Echo screen, two primary caps in one form, dialog or toolbar, something destructive (delete, remove, discard, reset…) on one press with no ConfirmButton, HoldButton or AlertDialog in the way, and a confirmation that says "OK" or its default "Confirm" instead of what happens. Each line quotes its rule by number. Three more rules are only hinted at, as notes that don't count: an AlertDialog for something that isn't destructive, a cap whose only job is to navigate, a field that's always read-only.
checkfinds what a keyboard, a screen reader or a request for less motion loses (echo-ui-cli). It reports a field with no label (noaria-label, no<label>around it or pointing at it), an error that's only a colour (aria-invalidwith no words tied to it; on an Echo field, the line says to use itserror), GSAP used straight in a file that uses Echo but not its motion, and an animation with no reduced-motion fallback. Tailwind palette classes (text-red-500,bg-black/40) now count as colours written by hand, since they never went through the theme.
checkcatches Echo restyled from outside, and says whatupdateneeds to know (echo-ui-cli). It reportsclassNameorstyleon a closed component, a stylesheet of yours reaching into Echo's classes, and a theme token set anywhere but the theme (a colour set there skips the contrast check). Checking the whole project also lists, without counting them, the Echo items edited since they were installed and the ones behind the registry, with theupdateline to run;--jsoncarries them asinfo. Offline, from the lock and the kept catalog. An edit inside Echo's own folder, which the check never reads, now tells the agent what it means (Echo's own file, or a new file of theirs in the wrong place) as hook context, and never blocks it.
checkholds the rest of the material to Echo's scales (echo-ui-cli). It now reports spacing and radii off Echo's scales, or on them but written as a number, with the token to write (padding: 13px→var(--echo-space-6),p-[13px]→p-[var(--echo-space-6)]); shadows drawn by hand, pointed to the shadow recipe that fits, and hand-drawn focus rings to Echo's ring; durations and eases a transition invents (300ms→var(--echo-duration-sweep)); and a second animation library imported in a file that uses Echo. Page layout past 32px, Tailwind's own utility classes andease-style keywords stay yours. A shadow is one finding, not one per colour inside it.
- Haptics: on a phone, the moments that matter buzz (
echo-ui-core). The sound tokens that mark a moment carry a haptic:selecta short tick when a choice changes,notifyandcelebratea success,cautiona warning,erroran error. So every component that plays one (checkboxes, radios, tabs, menus, dialogs) buzzes with no change of its own. A tap or a press never buzzes. Haptics don't wait for sound to be turned on, stay still in a hidden tab and before the first touch, and<SoundProvider haptics={false}>turns them off.playtakes{ haptic }to name another or none,useSound().buzzplays one on its own, anduseTactile's phases takehapticin the slot kept for it. Android browsers vibrate; iPhones can't, from a web page.
- What Echo needs from a Content Security Policy, written down and tested. The installation guide now says it exactly: Echo adds no scripts, evaluates no strings, starts no workers and loads nothing from another origin, so a strict policy works with
style-src-attr 'unsafe-inline'(a slider's position, a meter's level and Base UI's own parts ride on style attributes), plusimg-src data:for Textures andmedia-src blob:for VoiceNote. The guide prints a whole policy. A test holds the list to the code Echo ships, and every docs page is loaded under a report-only policy to catch anything the list doesn't cover.
- Shadow recipes: Echo's depths, by name (
echo-ui-core).createEchoThemenow declares 20--echo-shadow-*recipes next to the finish they read: wells (well,well-screen,well-deep,groove), raised parts (edge,hairline,chip,key,cap,cap-lit,panel,lifted,chassis), things that float (tooltip,popover,toast,modal), and focus and glow (ring,ring-inset,glow). For a part of your own, take one instead of writing a shadow:box-shadow: var(--echo-shadow-well). They're exported asSHADOW_RECIPES, listed in the theming guide, inllms.txt, incatalog.jsonand by the MCP server'sget_tokens, andmaterial.css's recipe classes read them.
inittells an agent this is an Echo project (echo-ui-cli). It writes three things, andnpx echo-ui-cli agent offtakes all three back out: a short Echo block, between<!-- echo-ui:start -->and<!-- echo-ui:end -->, inCLAUDE.mdorAGENTS.md(whichever the project has, both when it has both, once whenCLAUDE.mdimportsAGENTS.md, and a newAGENTS.mdwhen it has neither), with the exact package names, how to askwhichanddocs, styling only withvar(--echo-*), the accessibility floor and runningcheck; a Claude Code hook in.claude/settings.jsonthat runscheckon every file the agent writes and hands what it finds straight back; and an Echo skill with the procedures for building a screen from Echo's reference screens, adding a component, fixing what the check reports and updating Echo. Nothing outside the markers or the one hook entry is touched, a settings file that isn't valid JSON is left alone,--skip-agentleaves all three out, andagent onputs them in later.initstill never edits your source code, and the docs now say that instead of "never edits your files".
check: what an agent gets wrong about Echo, caught in its own code (echo-ui-cli).npx echo-ui-cli checkreads the project's.tsx,.jsxand.css, never Echo's own folder, and reports colours written by hand (named by the theme role they're nearest), raw elements where Echo has the component, Echo's classes borrowed onto raw elements, props a component doesn't take or can't go without, controls without the name their type requires, and markup a keyboard or screen reader can't use. Each line says the Echo way. It exits 1 when it finds something;--hookchecks the one file an agent just edited.catalog.jsongains what it reads: each component's API from its types (api), the standard HTML attributes, and the material recipe classes anyone may use.
whichanddocs: the CLI answers an agent's questions (echo-ui-cli,echo-ui-mcp).npx echo-ui-cli which "a settings toggle" "confirm a delete"names the item for each job, with a one-line reason and the install command, and says plainly when Echo has nothing for it yet (a date picker, today) rather than offering the nearest word match.npx echo-ui-cli docs switch fieldprints each item's props, smallest working use, accessibility needs and anti-patterns in one answer, with imports written the way the project resolves its Echo folder. Both answer offline from a copy of the catalog thatinitkeeps innode_modules/.cache/echo-ui/and thataddandupdaterefresh.initnow also installsecho-ui-clias a dev dependency, sonpx echo-ui-cliruns the project's own copy without downloading anything. The MCP server'ssearch_componentsleads with the same answerwhichgives, from the same function.
initsays what it added, in one line (echo-ui-cli): "Added echo.json, echo-lock.json, components/echo/echo-theme.css, echo-ui-core."--jsonlists the same things with where each landed. The line comes from one list of everythinginitputs in a project and how each thing comes out, which the docs will print too, and a test fails ifinitwrites a file the list doesn't name.
catalog.jsonsays its format and each item's version.catalogVersioncomes first, so a tool that keeps a copy can tell a format it knows from one it doesn't, and every item carries its/e/version, the oneaddwould install today. Only fields were added: nothing was renamed or removed. The MCP server'ssearch_componentsshows that version when/e/can't be reached.
SegmentDisplay: a seven-segment readout that looks like a real one. The segments a reading doesn't light stay faintly visible under the glass, and zeros that only pad a reading out print dim instead of vanishing, so88.5on a four-digit face reads as a dim0and a lit88.5. Digits, a minus, a decimal point, a colon and the few letters a segment face can draw;rollsteps through the digits in between. It sits in its own dark well, orflushinside a screen you already have. Screen readers hear the reading once, as it is, with its unit.
Tuner: a number picked the way a radio is tuned. A scale printed on the screen slides under a fixed, lit needle and aSegmentDisplayreading follows it. Drag it with your hand, flick it and it coasts onto a step; every tick that passes the needle clicks, and past either end it gives a little and springs back. It fits a big range into a small width: weight, height, a tip, a zoom level, a frequency. A slider for assistive tech, with the reading and itsunitin words and every slider key; the wheel only turns it once it has focus.
Keypad: a payment terminal's keys under its screen.amountfills from the right the way a till does (1, 2, 4, 0 reads 12.40) with its unused digits dim;pinlights a socket per digit and never shows or says them;digitsshows a code in its cells. An amount set from outside is read as a number, however many places it's written with. Type on a real keyboard and the key on screen goes down with your finger. One Tab stop with arrow keys between the keys (focusing the pad hands focus to one), the reading announced after each one, andactionfor the button under the keys.
FilterTray: filter pads in one long well, with the action at its end. A pad that's on is a lit cap with its name engraved under it; the action carries the count. Apply, and the lit pads fold into the action (Morph) and it becomes one key saying how many are on, their names engraved on the tray's floor beside it; press it, or Escape, and they come back out.
StatusIsland: background work in one small floating slab. At rest a pill with a lit socket and a readout; pressed, it blooms open wider first and then taller into a title, progress cells, a line of detail and small actions, and closes the other way round. It announces when the work starts, finishes or fails, never every percent.placement="top"or"bottom"floats it at the centre of that edge of the screen, clear of the notch and the home bar.
Creature: Echo, as a companion that reports what's happening.idle,busy,done,errorandsleep. As a vector it breathes, drifts, blinks and watches the pointer, and a new mood carries on from wherever it is; withvariant="dots"it's the same creature on a dot-matrix screen, moving in steps. Bring your owncharacter: path data for a body, the whites of its eyes and its pupils (and optionally rows of dots, or they're drawn from the paths). Echo supplies the materials, so any character relights with the theme. Hidden from assistive tech: the words beside it carry the state.
Receipt: the device prints. A summary feeds out of a slot in the chassis a line at a time, each with a small tick, in dark ink on the paper colour (held to AA under every theme), and waits to be torn off. Lines with dotted leaders, a total, a reference as a barcode, a footer. It prints when it arrives after the page has loaded, or again whenprintKeychanges; everything on it is text in the page from the start, so a screen reader never waits for the paper.
DateTile: the page of a desk calendar. The weekday and month engraved small on a raised tile, the day huge on the screen set into it; withroll, a new date turns over to its day.smsits in a list row (as anItem's media),mdon a card,lgon its own. It's onetimeelement read as a full date, worked in UTC from the ISO date you pass, so the day is the same on the server and in every browser.
WeekStrip: a week you tap through. Seven days in a largeSegmented, each with its weekday and date, the chosen one lit and the light travelling there as a drop. Today carries a lit rail, a day with something on it has a pip, and the keys at either end go a week back or forward, keeping the weekday. A radio group of days, each named with its full date.
DescriptionList: things and their values.plainsets the terms as machine labels over their values with the one separator between rows;screenputs the list on a dark screen with the values in the readout face, like a home screen's glance list.inlinesets them side by side. A realdl.
SwitchBank: settings that come in a grid. ATableofSwitches, one where each row meets each column (notifications by channel, permissions by role), with aCheckboxin each column's header that flips the whole column and shows a dash when it's mixed. Every Switch is named by its column and row.
JogWheel: a knob with no ends. Knob's own cap, well and pointer, larger, with a ring engraved on the cap: turn slowly for a step per click, spin it and the steps grow with the speed. The wheel turns it by how far it scrolled, either axis, once it has focus; over the hub, where a hair's move is a quarter turn, dragging waits for the hand to leave. A spin button for assistive tech;minandmaxare optional.
QuickTile: a quick-settings tile. A big molded key with a glyph, a name and a line of state; on, it's the lit cap and its socket glows. It presses like every Echo key. Aswitch, with details on a separate key (onMore), never behind a long press.
FillSlider: the tall slider of a control centre. Meter's tube and its lit plastic, standing up, following your finger anywhere on it; the top of the fill is the handle. Base UI's slider, vertical, with every slider key.
PatchBay: connecting things to things. Sockets down each side and a cable between every connected pair, for mapping an import's columns to fields or routing events to channels. Each source has aSelectthat says where it goes, and that's the control; the cables draw what the Selects say, drop into place and sway once, and can be dragged from socket to socket. On a narrow screen only the Selects stay.
Annunciator: the marks printed on a screen's glass. REC, SYNC, MUTE: every mark is always there, dark until what it names is happening, then lit. It says which are on, in words;liveannounces changes.
Ticket: a paper pass with a stub. What it's for on the main part, the code as a barcode on a stub across a perforation; use it and the stub tears off and drops away, leaving "Used". The same paper and ink asReceipt; on a narrow screen the stub goes under.
NowPlaying: a small player. A screen with the source, the title and the time in lit segments, aSliderto scrub, and previous, play and next in oneButtonGroup. Leave outduration(or pass an<audio>'sInfinity) for a live stream; itsNaNbefore the metadata loads reads "--:--". You own the audio; this is the device you control it with.
Perch: a creature on a card's top edge. It sits there watching, or hides behind the edge and peeks up when you're near (on hover and when focus is inside). The room it needs above the edge is kept, so it never covers content.
ShortcutMap: the "Keyboard shortcuts" sheet. The list of shortcuts inKbdcaps and, above it, a keyboard drawn in Kbd that lights the keys of the shortcut you point at and presses with your real keys. The list is the content; the board is a picture of it.
WidgetGrid: widgets people arrange. Fixed footprints (1×1, 2×1, 2×2, 4×2) on a grid that narrows to two columns on a phone. Arrange it and each widget gets a Move key (drag it, or use the arrow keys, and the new place is said) and, withonRemove, a Remove key; the others slide out of the way.
Scope: a colour per item, and softer plastic.accentgives what's inside a colour of its own for identity (a category, a filter pad, a person): the theme engine run on your colour, so its lit caps, glow and LEDs are derived and held to AA like your main accent, and every component inside picks them up. It says which one, never how it is.molding="soft"rounds the caps inside and fills them out with a pillow of light and a fuller shade;HardwareButton,QuickTileandKnobcarry the pillow, and look exactly as before outside a soft Scope.
- Reflective screens.
screen="reflective"gives a screen the paper glass of a pocket calculator, printed in ink: digits, labels and ghost segments in the chassis colour, nothing glowing. The ink is held to AA on the glass under every theme. OnSegmentDisplay,Tuner,Keypad,DateTile,NowPlaying,AnnunciatorandDescriptionList's screen.
- A colour per pad, and per creature. A
FilterTrayoption and aCreaturetakeaccent: the pad lights in its own colour, the creature is a family of colours, each through aScope.
- A
Segmentedoption can show more than a word. Give an option{ label, content }and the segment showscontent(a day and its date, a glyph and a pip) whilelabelstays its accessible name. The lit cap still travels between them the same way.
EmptyStatetakes anillustration.iconis for a small line icon and draws it quiet, at 28px;illustrationkeeps a picture at its own size and colour (aCreature, a drawing) and takes the icon's place when both are given.
HardwareButton:held. Holds the cap down from outside, with its click, the way a real key going down would, without firing the button or sending any event. For a drawn keyboard that follows the typing;Keypaduses it for the key you type on a hardware keyboard.
PullToRefresh: a list you pull down from the top to reload. The content follows your finger on the library's rubber band, a molded key turns as you pull and clicks at the detent, and the list holds there with the Loader's lights untilonRefreshsettles. The gesture is never the only way: the region's first tab stop is a real Refresh button, and one status says what happened.
Tabsjoin the size scale.sizeonTabs.Listmakes each tab 32, 44 or 60px tall, the same as a field or a button of that size, and it follows a surroundingSizeProvider. The default is 44px, a full target;smsuits a card's own views and grows to 44px on a touch screen.
- App navigation:
SideNav,TabBarandNotificationCenter. -SideNav: an app's sidebar from data: named groups, icons, count badges and nested sections that fold. The current page carries a rail that travels to it;collapsedshrinks it to icons, each with its name as a tip.renderLinkhands the links to your router. -TabBar: the bottom bar of a phone app, three to five destinations with icons, a count badge and a pip under the current one.fixedpins it to the bottom, clear of the home indicator. -NotificationCenter: a bell with an unread count that opens a list of notifications, unread ones marked, with "Mark all read" and an empty state.
- Choosing:
Combobox,TagsInput,ChoiceCards,CheckboxGroup,Fieldset,FormandHoldButton. -Combobox: a Select you can type into.multiplekeeps choices as tags that bud out of the text line;creatableoffers to add what you typed. Built on Base UI Combobox. -TagsInput: free-form tags in Field's well (Enter or a comma adds, Backspace takes the last back, a paste becomes several), withnormalizeto clean or refuse each one. -ChoiceCards: options that need more than a word, as cards; the chosen one lights a rail and drops its pip. One choice is a radio group,multiplea group of checkboxes. -CheckboxGroup: a named set of checkboxes with an optional parent that shows a dash when only some are on. -Fieldset: a named group of fields with a legend. -Form: fails well. Passerrorsto it and to each field; after a failed submit a summary at the top takes focus, counts the problems and links each to its field. -HoldButton: confirm by holding; the cap fills while pressed and drains if let go. A press that can't hold (assistive tech) arms it and a second press confirms.
- The rest of the inputs:
Toggle,Toolbar,Meter,RangeSlider, and two instruments,KnobandLevelMeter. PlusstartandendonField. -Toggle/ToggleGroup: the quiet toolbar key. On is the cap held down in its well, not a light; a group shares one well, with arrow keys between its keys,multipleor at most one. -Toolbar/ButtonGroup: caps sharing one long well. A Toolbar is one tab stop with the arrow keys, Home and End along it, andsmcontrols; Toggles and ToggleGroups inside join its order by themselves. A ButtonGroup is the look alone. -Fieldstart/end: a search icon or a currency sign engraved into the well before the text; a unit or a small key (clear, show password) after it. -Meter: a measurement in a range (storage, battery), a tube of lit plastic that settles to its level with a small slosh;marksengrave a limit without a second colour. -RangeSlider: two of Slider's knobs on one rail for a span, named "…, minimum" and "…, maximum"; they never cross. -Knob: a rotary cap with a lit scale. Drag it, wheel it once focused, or use the slider keys; it clicks at each step and settles onto each detent on a spring. -LevelMeter: a live level as LED cells with real ballistics and a held peak. It draws without re-rendering, stops once it's still, and tells a screen reader its level at most once a second.
- Six containers:
Card,Item,Stack&Grid,Collapsible,ScrollAreaandLink. The pieces every screen is laid out with, so nobody spaces a card by hand. All six are closed: their slots and spacing are props, not aclassName. -Cardis a Panel with an anatomy (media, a header with one action, a body, a footer whose controls aresm) andsizefor its padding. WithhreforonClickthe whole card is one link or button, named by its title, with one focus ring; it lifts as you reach for it, sinks a pixel as you press and sends Panel's ripple out from your finger. -Itemis the list row: media, title, description, trailing content, ruled by a hairline.swipeActionssit behind it: swipe it and they slide into view, and Tab reaches each one, sliding the row open the same way, so the gesture is never the only way in. -Stack&Grid(echo-layout) take gaps as steps on the space scale; a row wraps and a Grid's columns never get wider than the screen. -Collapsibleis one disclosure on its own, in Accordion's ruled style; closed content stays findable with find-in-page, which opens it. -ScrollAreascrolls inside itself with a groove and a molded thumb, shades whichever edge has more to see, and is a named region the keyboard can scroll. -Linkkeeps the words in the text colour and draws the accent as the underline (accent text on the chassis is under AA);externalsays it opens a new tab;rendertakes your router's link.
- One
sizeon everything with a height.Field,Select,NumberField,OtpFieldandSegmentedtakesize="sm" | "md" | "lg"(32, 44 or 60px), matchingHardwareButton, and all of them read aSizeProvideraround them.mdis exactly what they were.Avatargainsxs(inline with a line of text) andxl(a profile header).
- A new error shakes the field; a Select unrolls from its cap. When a
Field'serrorappears or changes, the field is shoved sideways and swings back through to rest on a spring, the way a latch that won't take the key bounces back. ASelect's list now grows out of the edge of the trigger it drops from, rather than scaling up from its own middle. Neither moves under reduced motion.
PullCordpulls a realSwitch. Its cap used to be a switch of its own. Now an EchoSwitchsits at the top as the fitting the cord hangs from, and it is the control: the state, the name (label), the focus ring, the keyboard, and a form value through the newnameprop (withid,disabledandrequired). Pulling the cord past the detent flips that Switch; flipping the Switch any other way gives the cord a tug. The cap is now a handle for the hand, hidden from assistive tech, so a screen reader meets one switch instead of a switch that looked like a bead. It installsecho-switchwith it. If a test of yours found the switch by its role and name, it still does; if it dragged the switch, drag.echo-pull-cord__bead.
<Panel pressable>: any Panel can give like water. Press the surface and a wave of dots runs out from where you pressed, bigger for a longer press; Enter or Space on a control on it sends one from that control. At rest it looks exactly like any Panel, it adds no role, and under reduced motion no wave runs. It'sRipple's wave, which now lives in Panel asuseRippleSurface;Rippleis that surface with its dots always out, and installs Panel with it. Two things change forRippleitself: it only draws while something moves (a wave, a press, the pointer), where it used to redraw every frame for as long as it was on the page, and a press on a button on it no longer thuds on top of the button's own sound.
- The Toaster piles its toasts and you can flick one away. Toasts used to stand in a plain column. They now pile like
ThrowStack's cards, the newest in front and each one behind a step higher and a little smaller, and fan out into the column while the pointer is on them or focus is in them (F6), with their timers paused. Drag one sideways and it turns from where you hold it; let go past the swipe and it leaves at the speed you threw it, still turning, instead of snapping back to fade. Under reduced motion nothing turns and a flicked toast fades where it was let go. Every dismiss button is still reachable from the keyboard.
- Digits that roll:
rollonLcdChip,StatTileandNumberField, and theuseRollhook under them. Passrolland a new value steps each digit through the ones in between, all together, fast when it's far and slowest on the last step: 18 → 21 shows 29, 20, 21. A falling number rolls back, 99 → 100 turns the nines over and puts a 1 in front, and anything that isn't a digit (a comma, a colon) swaps at once. The digits in between are drawn but hidden from assistive technology, so the value is still read once, as it is; under reduced motion it doesn't roll. OnNumberFieldit replaces the slide a step used to give the whole number, and typing never rolls. It's off by default, so nothing changes until you pass it.SplitFlapnow turns on the same drum (echo-roll, which each of these installs with it); it looks and sounds the same.
- The MCP server knows Echo's motion.
get_componentlists the motion names a component moves on, and 13 of them were namesget_tokenshad never heard of:morphFeels.cell,springs, the--echo-duration-*steps, and three that were not tokens at all (Ripple'swaves, SplitFlap'sflap, AddToBag'suseBudOff).get_tokensnow lists motion's whole vocabulary (the motion tokens, the spring presets, the morph feels, and the CSS duration and ease steps), and a test fails if a record names anything else. Ripple and SplitFlap now list none, because they time themselves; AddToBag listssprings.
Every entry carries its docs group, so search_components("motion") finds the sixteen motion components rather than Modal and Skeleton, whose descriptions happen to say "motion". Search also reads each entry's one-line summary. useTactile's record now covers the physics its entry exports, with examples for createSpring and useCapPhysics and what to do with a spring under reduced motion. llms.txt lists the same vocabulary.
- Six more that change shape as you use them, all on
Morph.SaveButtonnarrows into its own progress while your save runs, widens into a lit "Saved" and flows back.Timeropens into durations; the others run into the one you pick, which drains as the time runs and rings.Stopwatchbuds a Lap button from Start, and every lap is a drop that lands in the lap count.VoiceNoterecords from the microphone: your voice swells the Stop button, and the take becomes a note that fills as it plays.AddToBagsends a drop from the button into the count beside it.useBudOffis that drop on its own, from any element into any other.Morphnow keeps a button's identity across states when it has the samedata-morphname, and draws filled caps as levels.
- Six other kinds of physics.
PullCordis a switch you pull: the cord stretches, clicks at a detent and changes as you let go.SplitFlapis a readout that turns over flap by flap.LightSourcemakes your pointer the light the HardwareButtons inside it are lit by.Magneticmakes the buttons inside lean toward your hand before you touch them.ThrowStackis a stack of Panels you flick away with the speed you gave them.Rippleis a surface that sends a wave from where you press. All of them are built from Echo's own caps, panels and roles, have a keyboard path, and hold still under reduced motion.
- HardwareButton
fill. A cap can be a quantity:fill(0–1) pours the primary plastic into it from the start edge, under the label. The Timer and VoiceNote use it.
- The Motion gallery's first four: your own buttons, flowing into the buttons you need next.
Morphis the base. Wrap your HardwareButtons in it and render whatever state you like with plain React: when the buttons inside change, the old ones flow into the new ones as one liquid body. One becoming several pinches at the waist and comes apart; several becoming one folds; the same number move and resize in place, so a label that changes width flows to its new size. At rest there is nothing but your buttons; the body is drawn only during the change, in the cap's own material, with the labels riding on it, and it fades off the real buttons when it lands. Built on it:DoneButton("Mark as done" becomes a lit Done and an Undo),ConfirmButton(Delete becomes Cancel and a primary Delete, then "Deleted", which flows back) andActionFan(Share becomes its choices and a × that folds them in). Focus moves to the first new button when the one it was on goes away, Escape callsonEscape, the new state is announced, and reduced motion swaps the buttons in place. They needecho-ui-core0.2.0, which adds the morph engine (MorphSurface,morphFeels,outline,apart) toecho-ui-core/tactile. They're live at/motion.
- The site asks not to be indexed until the packages resolve.
robots.txtservedDisallow: /, and every page carriesnoindex— both tied to the same release flag as the "not released yet" banner, so they come off together the day the install works. Until then the install lines a search engine would pick up are lines that cannot run.
- The installation page says what Echo costs. Five real builds of the same Vite + React app with different amounts of Echo in it, each measured as every asset the production build emits, gzipped, against the same app with no Echo at all. A component's own code and CSS, with the theme and
echo-ui-core, is 1.9 kB; a button is 31.5 kB because it brings GSAP; a form is 80.5 kB because it brings Base UI; all fifty items together are 194 kB. The page leads with the first figure, because the others are mostly two libraries it already names and quoting them without saying so would be true and useless.
echo-ui diffandecho-ui update— the upgrade path. Echo copies components into your source tree, which means that from the moment you install one, your copy and Echo's diverge and every later fix is invisible to you. Until now the only way to take one wasadd --overwrite, which throws away whatever you changed without showing you what it is throwing away.
npx echo-ui-cli diff reports every installed item as up to date, behind, edited or edited and behind; name an item and it prints the unified diff between your copy and the version the registry serves. npx echo-ui-cli update moves the items nobody edited and stops at the ones somebody did, naming them and the two commands that resolve them. An item moves whole or not at all, so you are never left with half a component at one version and half at another, and nothing you wrote is replaced without --overwrite.
Edited and behind is counted per item, not per file: change field.tsx while a fix lands in field.css and the item is edited and behind, not merely edited — which is the case that would otherwise leave the fix sitting in the registry unread.
update does not merge, deliberately. A merge that goes wrong leaves broken source in a file you were told you own, so update moves only what it can move cleanly and stops everywhere else. diff plus --overwrite gives you the same information and leaves the decision yours.
echo-otp-field— the one-time code a sign-in asks for, as a row of lit digit windows. A pasted code fills the row, arrow keys and Backspace walk it, andautocomplete="one-time-code"lets a phone offer the code it just received.
echo-chat— a template block: a chat screen wired through oneonSendcallback.
RadioGroupPropsis now exported fromecho-radio-group, like every other component's props type.
- The MCP server is publishable as
echo-ui-mcp. It bundles the agent layer instead of importing a private workspace package, runs compiled JavaScript instead of TypeScript through tsx, and reports its real version instead of0.0.0.
- Usage, composition, accessibility and motion & sound in the agent layer.
catalog.json,llms-full.txtand the MCP server'sget_componentnow carry these sections for every item whose docs have them. Sections follow the component page's order, so Props now comes after Examples. A catalog built before this change still renders; it just has no new sections.
echo-tabsis fully documented — usage, composition, accessibility, motion & sound and a one-line feel — the first item written to the new component page template.
- Every item is documented to the component page template — usage, composition, accessibility, motion & sound and a one-line feel, in the docs,
llms-full.txtand the MCP server.
- Setup an agent can follow on its own. The MCP server's instructions and
llms.txtnow say what a fresh app needs beyond the install: injectcreateEchoTheme().css(components are colourless without it), add aSoundProvider, and mark pages that pass callbacks"use client". The template usage snippets wrap the screen in<main>.
echo-ui-mcpis ready to install from npm —npx -y echo-ui-mcp— with a README written for the people installing it.
- Docs guides. Installation, Components, CLI, Registry, Theming, Typeset, Motion & sound, MCP server, For AI agents and this Changelog each have a page, grouped in the sidebar by reader (developers, designers, AI-assisted builders). Token tables, tool lists and install lines on them are rendered from the registry, not copied.
- Text styles. Six named styles (
machine-label,body,body-sm,caption,heading,readout) as--echo-type-*tokens, plus weight, line-height and letter-spacing scales. See Typeset.
- Layering, border, icon and breakpoint scales:
--echo-z-*,--echo-border-*,--echo-icon-*, andBREAKPOINTSfor JavaScript. See Theming.
- Right-to-left. Every component mirrors under
dir="rtl"with Base UI'sDirectionProvider; the light stays at the top left. See Installation and the right-to-left gallery.
- Help text and errors on
FieldandTextarea:descriptionanderrorprops, linked to the control witharia-describedby, witharia-invalidwhile there's an error. The error is words and a square pip, not a colour. Both now build on Base UI'sField, so re-adding them also installs@base-ui/react.
ToasteranduseToast(). Mount<Toaster>once;toast({ title, body, tone })shows and announces a toast. The live region is there before anything is said, so it's heard. Built on Base UI's Toast: queue, timeout, a dismiss button, andpriority: "high"for what can't wait. Breaking:Toaston its own is now a display slab withoutrole="status"; to announce something, useuseToast().
Segmentedcan switch off one segment: pass{ label, disabled: true }inoptions.
- The agent brief carries the accessibility rules. The MCP server's instructions and
llms.txtnow say what a consumer can still get wrong: names on controls,descriptionanderroron fields, oneToasterfor announcements,autoCompleteon sign-in, and that state is never colour alone.
Changed
- Components whose shadow is a recipe read it by name. 71 shadows in 54 items now say
var(--echo-shadow-<name>, <the same shadow>), so they look exactly as they did (every docs page was compared before and after) and follow a theme's recipes. Scope declares the recipes that read the accent again, so a Scope's own colour still reaches its focus rings and glows, and reflective screens do the same for theirs. Shadows that move with their part (a cap's drop as it's pressed) stay in the component.
- Sound starts off until someone turns it on (
echo-ui-core).SoundProviderstarts muted for anyone with no stored choice, so a first visit makes no sound nobody asked for; the choice they make is remembered as before, and always wins. Give people a switch (useSound().setMuted), or pass the newdefaultMuted={false}where sound is the point from the first press. An app that relied on sound playing by default now stays silent until the person asks for it.
- Echo's two sentences, where people meet it first (
echo-ui-core,echo-ui-cli,echo-ui-mcp). "Tactile components you can feel. The design system that keeps your AI on-brand." leads the README and the site's link preview, and opens each package's npm description. The README and the installation page also say, in one wording, what Echo already does: updates keep your edits,initdoesn't touch your CSS, few dependencies, first-party code only, and accessibility tested against WCAG 2.2 AA and kept bycheck, with what isn't covered yet.
- The catalog's groups say what you're building. The
groupon each component, which the docs sidebar,llms.txtand the MCP server'ssearch_componentsshare, is now one of Foundations, Buttons, Forms, Overlays, Navigation, Layout, Data display, Feedback, Objects, Motion, Creature, Texture and Templates. Controls, Structure, Surfaces and Primitives are gone: their items moved to the group you'd look in, and the hardware only Echo has (Knob, Tuner, Keypad, Receipt and the rest) is together under Objects. Nothing you install changes.
npm fundleads somewhere.echo-ui-core,echo-ui-cliandecho-ui-mcpname echo-ui.com/support as their funding page, where Echo takes one-time and monthly donations. Nothing about installing or using them changes.
- Echo's default colour is purple.
createEchoTheme()with no accent,npx echo-ui-cli initwithout--seed, the seed schema's defaults and the site all start from#8B6FE8, on a purple-tinted chassis (#232130) and paper (#EFEBFA). The raspberry the device was first drawn in is one seed away:{ accent: "#DE3B74", chassis: "#26222B", paper: "#F5EDE0" }. The site's theme presets are the Nawwara seed (the purple), Raspberry, Coral and Sky; Teal and Amber are gone. Every comment in the components that called the accent "raspberry" or "pink" now calls it the accent.
- One way to install. Items are served in Echo's own format under
/e/, whichnpx echo-ui-cliand the MCP server read. The older per-item files under/r/are gone;/r/catalog.jsonstays where it was.
TabBaris a dock. It was a flat strip whose current place only changed its icon's colour. It is now a molded slab floating clear of the screen's edges, with a recessed track and one raised cap seated under where you are; every destination has an LED socket and only the current one is lit. Go somewhere else and the cap lifts, travels there on a spring and settles in. Same props; under reduced motion the cap moves straight there.
- Comments only. A few source comments used screens that are no longer in the library as their example; they now make the point without them. Nothing these components render or do changes.
- A
Meterasked for a percent reads its share of the range.format={{ style: "percent" }}formatted the raw value, so 78 read "7,800%", on screen and to a screen reader. It now reads "78%", with your other format options kept.
- A control at the start of an
Itemcan be pressed. A done box inmediasat under the row's link, so pressing it opened the row instead. It is now its own target, like a control intrailing; an avatar or icon there still opens the row.
- Breaking: HardwareButton's sizes are the control heights, so a button lines up with a field. A button's outer height, well included, is now
sm32px,md44px,lg60px, the same as aField,Select,NumberFieldorOtpFieldof that size. Before, the default button was 60px beside a 44px field, and no size of button lined up with any input, so a search bar, a filter row or an inline "invite" form never looked right.
What to do: the old default look (60px) is now size="lg". Give it to the one action a screen is built around (sign in, checkout, an empty state's call to action) and leave the rest at the new md. The old lg (68px) has no equivalent; it becomes 60px. sm goes from 48px to 32px, for toolbars and table rows. A press lands anywhere on the button, well included, so md is a 44px target, and on a touch screen sm grows to 44px. LedKey joins the same scale, which takes its default from 40px to 44px.
New with it: fullWidth, so a button can take its row without reaching into the button's CSS, and SizeProvider (echo-size), which sets the size for everything inside a toolbar, a filter bar or a card footer. A size passed to a control wins.
- A morph's text no longer blinks out at the handover. As one set of labels gave way to the next, the new ones started a moment late, so for a frame both were faint and the button read as blank. They now start together; the words cross-fade without a gap. Everything built on
Morph(DoneButton, ConfirmButton, ActionFan, SaveButton, Timer, Stopwatch, VoiceNote) gets it.
PullCordstops working when it isn't moving. Its cord was simulated every frame for as long as it was on the page, hanging still or scrolled out of sight. It now sleeps once the cord comes to rest and wakes when it's pulled, tugged or resized: the same cord, without the battery cost.
- Pagination's current page no longer spills past its cap as it lights. Landing on a page flashes the key light out from its middle; the flash grew to 1.4 times the cap, so for a quarter of a second the page was wider than its box. It now blooms to the cap's edge and fades there.
Fieldis the same height asSelectandNumberField. Its input kept the browser's own 1px padding above and below on top of its 44px, so a Field stood 46px tall beside a 44px Select and the two never lined up in a row. It is now exactly 44px; everything below a Field moves up 2px.
- A toast's title and body no longer sit apart. In the toaster they are an
h2and ap, and the browser's own margins opened a 21px gap between them that<Toast>on its own doesn't have. They now sit together, and the toast is as compact as the slab.
echo-pull-cordno longer pushes a narrow screen sideways before it loads. The cord's drawing was rendered at the width the cord would like to swing in (348px at the default length) and only narrowed to its container once the script ran, so on a phone the page scrolled sideways until then, and for good without JavaScript. The drawing now takes the cord's own box from the start. Nothing changes once it has loaded.
echo-ui-core0.2.0 is on npm. Components installed since the motion engine landed imported names 0.1.0 doesn't have (useCapPhysics,usePressable,useWellMotion,createSpringand more), so a fresh install failed to build. 0.2.0 exports every name the components use, and it adds the morph engine the motion items (DoneButton,ConfirmButton,ActionFanand the rest) are built on. If an install failed for you,npm install echo-ui-core@^0.2.0fixes it.
- Comments only, no behaviour change. Comments in the component source used to cite documents, milestones and ticket numbers from Echo's own planning, none of which exist in your project. The citations are gone and the sentences around them stay. Where a comment was only a citation, it now says what it meant. Three development warnings (
HardwareButton'sengraved/captionchecks andStatusDot's missing-label check) also dropped the ticket code at the end of the message.echo-ui diffwill show changes in most items; none of them are to code.
- An item's version is now a function of declared data, not of how the changelog is formatted. The version an item ships under is published once and never rewritten, and it used to be inferred from prose: a regex for a bolded "Breaking" run across the whole unreleased section, and attribution by "does a bullet mention this name in backticks". One breaking entry therefore moved every item that changed that cycle, and an entry explaining that "
MenuandSheetare unchanged here" filed itself as a change to Menu and Sheet — which it really had.
Every entry now carries a <!-- bump: …; items: … --> line that markdown hides and the build reads, and the build refuses a changelog with an entry that declares neither or that names an item which does not exist. The bump is patch, minor or breaking; turning that into a number is still code, because it depends on Echo being 0.x.
What you can see in /e/: each item's changes list is now the entries that DECLARE it, so several items gained entries that always applied to them and a few lost entries that never did. The breaking flag comes from the declaration. No item's files changed, so no item's version moved.
- Breaking:
tonemeans one thing now. It meant three.echo-bannerused it for semantic status (info/active/warn/danger),echo-toastfor activity state ("default" | "active"), andecho-count-badgefor a colour choice ("accent" | "neutral"). Learningtoneon one component taught you nothing about the next, and Toast's"active"and Banner's"active"were not the same idea.
There is now one vocabulary, EchoTone in echo-ui-core/types: info (the neutral default), active (happening now), warn (needs attention), danger (something is wrong). tone is what a thing means; variant is what it is made of — the split Echo already used for PanelVariant, SkeletonVariant and HardwareButtonVariant.
Changed: echo-toast, echo-count-badge, echo-banner.
What breaks. On echo-toast, tone="default" is now tone="info" — rename it; it is the same slab. On echo-count-badge, the tone prop is now variant and the exported CountBadgeTone type is now CountBadgeVariant — it was never a status, and variant is what a colour choice is called everywhere else in Echo. echo-banner is unchanged for callers: its four values were already the right ones, and BannerTone stays as an alias of EchoTone.
A component may implement a subset of the vocabulary, the way size is "sm" | "md" | "lg" on some components and "sm" | "md" on others. Toast takes info and active only: its slab renders one LED, and accepting danger while rendering it identically to info would be a worse lie than not accepting it. Widening it is a design change, not a rename.
- Breaking: every control takes a
ref, anonBlurand anonFocus— and the two that were wide open are now closed. Four of 49 components could receive a ref, andonBlurappeared in zero public prop types, soreact-hook-form's uncontrolledregister()— which needs exactlyref,onBlurandname— could not work with any Echo control.
All twelve controls now accept the declared interop set from PRINCIPLES.md: ref, id, name, disabled, required, readOnly, autoFocus, onBlur, onFocus, onKeyDown, aria-* and data-*. Text inputs also take placeholder, autoComplete, inputMode, maxLength, minLength and pattern; buttons also take onClick, type, form, value, title, onPointerDown and onPointerUp. The set lives in echo-ui-core/types as EchoInterop, EchoTextInterop and EchoPressInterop, so it cannot drift between components.
Added to: echo-field, echo-textarea, echo-select, echo-checkbox, echo-switch, echo-radio-group, echo-slider, echo-segmented, echo-number-field, echo-otp-field, echo-hardware-button, echo-led-key.
What breaks. echo-textarea, echo-hardware-button, echo-led-key and echo-radio-group used to extend their DOM element's attributes wholesale and accept className. They no longer do — that is the closed-API policy in PRINCIPLES.md, and those four were the exception, not the model. If you passed className to one of them, style it from the parent through the component's own class instead (.your-row .echo-textarea { … }); Echo's own echo-chat template was changed the same way. If you passed some other DOM attribute, check the interop list above — if it is genuinely plumbing and missing, that is a bug worth reporting.
ref is implemented with forwardRef rather than as a plain prop, deliberately: on React 18 a function component never receives ref through props, so the plain-prop version would have type-checked, worked on React 19, and populated nothing for React 18 users. Echo supports both.
- Breaking: Echo's public API names Echo's own types, not Base UI's. Eleven items put a
@base-ui/reacttype straight into a signature you write against —BaseSwitch.Root.ChangeEventDetailson a change handler,ComponentProps<typeof BaseSelect.Portal>["container"]on a portal target. That made Echo's API a downstream of Base UI's internals: an upstream rename would have been a breaking Echo release triggered by somebody else's refactor.
echo-ui-core/types now holds the Echo-named equivalents — EchoChangeEvent, EchoSliderChangeEvent, EchoNumberFieldChangeEvent, EchoDismissEvent and EchoPortalContainer. They are structural subsets of what Base UI passes, so the same object still arrives at runtime and nothing is wrapped or copied; only the name you write down changes. Base UI remains the implementation.
Changed: echo-switch, echo-checkbox, echo-slider, echo-number-field, echo-radio-group, echo-alert-dialog, echo-command, echo-modal, echo-popover, echo-select, echo-tooltip.
If you annotated a handler's second argument with the Base UI type, change it to the Echo one; if you let it infer, nothing to do. Menu and Sheet still pass Base UI props through and are unchanged here — that is a separate piece of work.
- The agent brief says Echo has not shipped.
llms.txtand the MCP server's instructions led withnpx echo-ui-cli init, and that name on npm is a reserved stub with no executable — so a model following the brief wrote an install step that fails with npx's own error. The brief now opens with the caveat, and the docs carry it on every surface that shows a command. One switch (CLI_RELEASED) silences all of them at launch, and a test refuses to let one move without the others. Affectsall componentsonly in what the brief says about installing them; no item's props, CSS or rendered semantics change.
- Echo is released.
echo-ui-core,echo-ui-cliandecho-ui-mcpare on npm at0.1.0, and the commands the docs have always shown now do what they say:
npx echo-ui-cli init
npx echo-ui-cli add hardware-buttonUntil today those lines carried a notice saying they could not work — echo-ui-cli was a reserved name with no executable, and the published echo-ui-core was older than every registry item required. Both are fixed, the notice is gone, and the registry at https://echo-ui.com/e is live and serving all 50 items.
- Echo's home is
echo-ui.com. The registry, the docs and the packages all move to it fromecho.nawwara.studio. Nothing was published under the old host, so noecho.jsonanywhere points at it and there is nothing to migrate — this is the last release where that is true.
What it changes for you: npx echo-ui-cli init writes "registry": "https://echo-ui.com/e", and every install line, the MCP server's catalog and the llms.txt files name the new host.
- Controls must have an accessible name, or they don't compile.
Switch,Checkbox,Slider,Segmented,Select,RadioGroupandProgressneedaria-labeloraria-labelledby;FieldandTextareaneedlabel,aria-labeloraria-labelledby; aniconOnlyHardwareButtonneedsaria-labeloraria-labelledby. Breaking for code that left them out: add the name. A control inside a<label>pointsaria-labelledbyat the label's text.
- Tag and Banner name their buttons after their content: "Remove Loop", "Dismiss Offline mode", instead of a row of identical "Remove" or "Dismiss" buttons.
removeLabelanddismissLabelstill override.
- One scale per dimension. Radius, type, spacing, duration and control height each come from a single theme scale, and component CSS may no longer hardcode any of them. Re-adding an item picks up the new values; a local edit that hardcoded one of these will be overwritten.
- Templates reworked: every template is usable at 390px.
- One description per item, everywhere.
get_component,llms-full.txtandcatalog.jsonnow use the same description as the item's docs page; they used to use a shorter copy that had drifted from it in 49 of 54 items. The short form rides along assummary, whichllms.txtandsearch_componentslist.
- Descriptions that described an older look.
Tabs(a lit rail, not a lit cap),Select(CSS fades, not a GSAP exit), andBadge,Segmented,SelectandField, which named the default seed's raspberry instead of the accent.
- Status is carried by shape, not only colour.
StatusDotstates andBannertones differ by luminance and form on every shipped seed and under colour-vision deficiencies.
- Weights, line heights, letter-spacing, z-index, border widths and icon sizes come from the theme scales in
all components, with the old value as a fallback, so components keep working withecho-ui-core@0.0.1. Strays moved to the nearest step: most by under a pixel; HardwareButton icons are 14/16/20px (were 15/17/19).
- HardwareButton's hover glint is a background sweep inside the cap instead of an element outside it. It looks the same.
Fixed
- Segmented's unchosen options read (Segmented). Their labels were the text at half strength over the well, 5.8:1 by WCAG 2 and APCA Lc 36 on Echo's own device, a colour the theme never checked. They're the theme's dim text now, held to AA on the well (BUG-175).
- Small lit text reads, and a button on a Panel has an edge, on a dark device (
echo-ui-core). Lit text (--echo-accent-ink) was the glow itself, and passed WCAG 2 at 5:1 while reading at APCA Lc 30 to 43 on every seed; on a deep cobalt a "Checked in" badge hurt to read. It's now the glow lifted, same hue, until a small label reads at Lc 60 on every dark ground; LEDs, rails and halos keep the glow (BUG-174). On a near-black body a cap stood off the body at 1.19:1 and its rim off the cap at 1.34:1, so a button on a Panel had no edge; a darker body now steps its caps further until they clear what every body Echo was tuned on clears, and those bodies keep their values exactly (BUG-173). Dim text reads at Lc 48 to 56 on a dark device and is reported, not moved.
whichnames the part for more of the ways people say a job (echo-ui-cli, the MCP server'ssearch_components). "A row of choices where one is chosen", "a status word", "group related content", "a departure time that flips" and "a person's initials in a round chip" used to answer "Nothing in Echo does this yet", or Tag, and an agent told that draws its own lookalike. They name Segmented, Badge, Panel, SplitFlap and Avatar now, and "a photo frame" says to put the image in a Panel (BUG-172).
- A light device's recesses keep the body's hue in Chromium (
echo-ui-core, BUG-166). Chromium drops the hue of a colour under 0.02 chroma when it mixes in oklch, and draws the mix at hue 0, a rose. A light device's plastic is that grey, so on a lilac body every field, track and rail was pink. A mix made only of greys is made in oklab now, which has no hue to lose; a mix with an accent in it stays in oklch. On a dark device nothing you can see changes.
- The page's ground gives it Echo's type too (
echo-ui-cli,echo-ui-core, BUG-165).init, the setup line in the agent files and the page check's advice gavebodya background and a text colour but no typeface, where the installation guide gives all three. Pasted as printed, the page's own words fell back to the browser's serif, and a style's type never reached them. All of them now givefont-family: var(--echo-font-sans)with the ground.
initprints a theme import that works (echo-ui-cli, BUG-159). It said to pasteimport "./echo-theme.css", which resolves only from inside the Echo folder, so in the root layout it was "Module not found". It now names the file (app/layout.tsx,src/app/layout.tsx, or Vite'ssrc/main.tsx) and gives the import that resolves from it: through the project's alias when it has one (@/components/echo/echo-theme.css), relative to that file when it doesn't.
- The page check stops asking for AA on a disabled field's label (
echo-ui-core, BUG-150). A disabled control's label and help text sit beside it, not in it, so<EchoCheck />andcheck --urlmeasured them as live text and said to fix them. WCAG exempts text that is part of an inactive control, and what names or describes a disabled control now counts as part of it.
- A primary cap's label is readable under a Scope's soft molding (BUG-147). The pillow of light across the top reached behind the tops of the label's letters and took them to 4.2:1. On the primary cap it's as bright and fades out sooner, so the label keeps the 4.5:1 the lit plastic has.
- WeekStrip's weekday letters are readable (BUG-145). Faded on top of the unchosen segment's dimmer text, the letters measured 3.6:1 on the screen behind them, and the chosen day's 3.9:1, under the 4.5:1 that text their size needs. They're quieter than the date by size alone now.
- A lit QuickTile's status line is readable (BUG-146). Faded to 80% on the lit plastic it measured 4.4:1. On a lit tile it's at full strength now; an unlit tile's is unchanged.
echo.json's$schemapointed at nothing (BUG-144).initwrites"$schema": "<registry>/schema/config.json", and no build published it, so an editor couldn't check the file. The registry now publishes it: each field, the valuesframeworkandpackageManagertake, and which fields every project has.
- On a phone, a tapped part no longer stays lit (BUG-143). A phone leaves what you tapped under
:hoveruntil the next tap, so a table row stayed shaded and a trigger stayed lit after the finger lifted. Hover styles now apply only where there's a pointer that can hover (@media (hover: hover)), in all 23 components that had one. Checked, active and focus styles that shared a rule with them still apply everywhere.
- Every command the CLI prints is one you can run (BUG-138,
echo-ui-cli).diff,updateand a lookup of an item that isn't installed told you to typeecho-ui update,echo-ui diff <name>andecho-ui list.echo-uiis on no shell's PATH unless the CLI is installed globally, so each of those was "command not found". They now saynpx echo-ui-cli <command>, like every install line.
- RadioGroup's and HardwareButton's docs list the name their types require (BUG-137). A RadioGroup without
aria-labeloraria-labelledby, and aniconOnlyHardwareButton without one, are type errors, but neither name was in the prop table,llms-full.txtor the MCP'sget_component, so an agent reading the docs was never told the control needs a name. Both now documentaria-labelandaria-labelledby. Only the docs changed; no installed file did.
- AlertDialog's destructive confirm reads on every accent (BUG-135,
echo-ui-core). Its label is the accent ramp's palest stop on its two deepest, and on green accents it read 4.38:1: about one accent in ten, whatever the chassis and paper. The theme now lifts--echo-accent-1just far enough for that label to clear AA; an accent that already did keeps its ramp exactly.
ensureContrastandensureBackgroundContrasthead for the side that can pass (BUG-134,echo-ui-core). Both chose between lightening and darkening at luminance 0.5, but black and white give a colour the same contrast at about 0.18. A mid grey ground was pushed toward white and stopped at 3.48:1 when black would have reached 5.9:1. In a theme with a mid-tone paper, the primary cap was lightened into its own label and read 1.00:1.
- A seed with a darker paper no longer puts unreadable labels on the caps (BUG-133,
echo-ui-core).createEchoThemefitted the primary cap to the paper as you typed it and only then corrected the text against the chassis, so a dark paper on a dark chassis got a cap fitted for dark text under a label that had been turned light: the primary button, the accent Badge and the selected Segmented option read 1.00:1, with no warning. The text is now corrected first, against the neutral caps as well as the chassis, and the caps are fitted to the text the theme emits. The four shipped seeds produce exactly the same theme as before.
- A component held to its container's width stays inside it, padding and all (BUG-128).
HardwareButton,Badge,Tag,LcdChip,StatTileandShortcutMap's board are capped at their container so a long label truncates or wraps, but the cap left out their own padding: aHardwareButtonwithhrefcould reach 36px past a narrow column. The cap now counts the padding. A<button>cap looked right already; a link cap now matches it.
- A key typed on the keyboard stays down while it's held (BUG-120). A Keypad key typed on a real keyboard went down on screen for 90ms, however long the finger stayed on it. It now comes up when that key is let go, or when focus leaves the pad, and a quick tap still shows.
- A small
HardwareButtonwith a short label is a 44px target on a touch screen (BUG-119). It grew to 44px tall but stayed as wide as its label, so "$5" or "OK" was 40px wide. It's now at least 44px wide on a coarse pointer too.
- A
Receiptalways comes out whole (BUG-118). When its effects ran a second time while it was printing (every effect does in development under StrictMode, and<Activity>does it when a hidden part is shown), the print stopped with the paper still in the slot, so a Receipt in a dev build arrived blank. A print that's interrupted now ends with the paper resting whole.
- An
AppShellinside abareone keeps its own sidebar (BUG-117). The bare shell's rule that hides its sidebar reached every shell inside it, so anembeddedscreen framed on a bare page (a template in a landing page, say) lost its sidebar and its content fell into the sidebar's narrow column. The rule now applies to the bare shell's own sidebar only.
HardwareButtonno longer cuts off what's in its icon slot (BUG-114). The label's truncation rule also clipped the icon, so a digit or letter used as a key's icon lost its bottom edge, and so did any icon drawn to the edge of its box. The icon slot now centres what's in it and clips nothing; the label still truncates.
- The MCP server's
search_componentsunderstands a query in several words (BUG-111). "radio tuning scale" found nothing, because the whole query had to appear as one phrase; it now finds Tuner. A phrase match still ranks first.
LcdChiprenders aspan, so it's valid inside a button, a label or a line of text. InsideStatusIsland's pill it was adivin abutton, and its own name hid the reading from the pill's (BUG-109). Its look and its props are unchanged; event handlers now receive anHTMLSpanElement.
Stopwatch: Pause takes on every press in Safari (BUG-86). The running time is the cap's label and was rewritten every frame by replacing its text, so a press that began on the old text and ended on the new one was sometimes dropped. The time now changes in place.
Stopwatch: a lap still in flight when you Reset no longer comes back (BUG-74). Its drop landed after the reset and put the old count back, so the next first lap briefly read "2 laps". To do it,budOff(…)fromuseBudOffnow returns a function that calls the drop off before it lands; ignoring it changes nothing.
HardwareButtonstays a cap when something passes it a class or a style (BUG-73). Menu's own example,<Menu.Trigger render={<HardwareButton>…</HardwareButton>} />, rendered a bare button: Base UI hands the trigger's class to the element it renders, and it replaced the cap's. Astylereplaced thefillthe same way. Both are now added to the cap's own.
PullCord: holding Space is one tug (BUG-72). Each auto-repeat of a held Space or Enter tugged the cord again, so the switch flipped on and off for as long as the key was down.
Rippleunder reduced motion shows its dots (BUG-71). The still surface was drawn once and then cleared by the canvas being sized, so it was blank from the start and after any resize.
ThrowStackunder reduced motion puts a thrown card back straight (BUG-70). A card thrown by dragging went to the back still shifted and tilted by the drag.
VoiceNotenever leaves the microphone open (BUG-69). Two presses on the microphone before the browser granted it opened two streams and closed only one, so the recording light stayed on; leaving the page while it was still asking did the same. The second press is now ignored, and a stream granted after the component let go is closed straight away.
- The MCP server sees a new release without a restart (BUG-56). The registry index was meant to be read again every five minutes, but the client under it kept every document it had read, the index included, and each component's record was kept for as long as the server ran. A server started before a release kept describing the old one (its versions, its
echo-ui-coreranges, its dependencies) whilenpx echo-ui-cli addinstalled the new one. The index now really expires, and a component's record is read at the version the index names.
- The MCP server says so when the registry serves a file that fails its hash (BUG-57).
get_componentandget_accessibilitydropped the "Ships as" section, the same as when the registry could not be reached, so a tampered registry looked like one that was down. They now answer with an error naming the file.
Checkboxkeeps youronBlurandonKeyDownand its own (BUG-58). The caller's handlers replaced the cap's instead of running before them. With react-hook-form'sregister(), holding Space and tabbing away left the cap sunk, and a caller'sonKeyDownstopped Space from pressing the cap at all. Select, RadioGroup and HardwareButton already merged them.
- Focus is scrolled clear of the rail, not under it (BUG-33, WCAG 2.4.11 Focus Not Obscured). Tabbing backwards up a long page scrolled the newly focused element to the top of the scrollport, which is behind a rail that is
position: sticky; top: 0— so on a long docs page the thing you had just tabbed to was invisible.echo-app-shellnow setsscroll-margin-block-starton.echo-shell__contentand its focusables, derived from--echo-shell-rail-h, so a consumer who retunes the rail's height moves the offset with it.
Found by running the suite on a second engine for the first time, and it took that second engine to see: Chromium's backward-tab scroll happened to land clear of the rail, which was alignment luck rather than a contract — the same page fails on Chromium once reproduced in isolation.
/e/served files that a fresh clone would not have (BUG-34). The registry build wrote over its output directory and never removed anything, so an item that had been renamed or deleted kept resolving — but only from the machine that built it. Since the deploy runs from a working copy, that machine's leftovers were what the world got, and the URL would have 404'd the first time anyone built from a clean checkout. The build now clears/e/first, which is safe because everything it serves is either regenerated from the sources or copied from the released archive.
- The Playwright fixture routes are no longer published.
/checks/*and/stressexist so specs have something to drive; they were prerendered into the deployed site along with the docs and the registry. The static export drops them, and refuses to produce a deployable directory whose/e/index.jsonis missing or unusable./gallery,/auditionand/tactilestay — they are linked from the nav and the guides, so they are documentation, not scaffolding.
- Props the docs did not list.
HardwareButton(icon,trailingIcon,target,rel),Modal(trigger,title,description,footer),Popover(trigger,title),Tooltip(trigger),Slider(value,onValueChange,aria-label,aria-labelledby),NumberField(onValueChange),SoundProvider(backend) andTextured(dither,noise) now appear in the prop tables,llms-full.txtand the MCP server.
- Sign-in fields had no
autocomplete(WCAG 1.3.5).Fieldtakes anautoCompleteprop (email,current-password,one-time-code), so autofill and password managers fill a sign-in.
- An indeterminate
Checkboxlooked checked. It drew the same checkmark as a checked box, so "some selected" and "all selected" differed only inaria-checked. It now draws a dash.
- Examples that did not compile. The motion example passed strings to
useTactileinstead of{ motion }phases; theuseGsapPresenceexample omitted theactionsRefcast Base UI needs; theCommandkeywords example and theAppShellmetrics example were not valid code.
- Popups opened inside a dialog appeared behind it. A
Select,Popover,TooltiporMenuinside aModalorSheetopened under the dialog's backdrop. They now stack above it.
- A trigger element from a server component was wrapped in a second button on a Next.js dev server, for
Modal,AlertDialog,PopoverandTooltip. It now becomes the trigger itself.
Segmentedin right-to-left, and with an out-of-rangevalue. Arrow keys were hard-coded left-to-right, and avaluethat matched no segment left no tab stop, so the control couldn't be reached by keyboard. It now builds on Base UI's RadioGroup, so re-adding it also installs@base-ui/react.
- Field, Textarea and NumberField's focus ring was too faint (2.6:1, under WCAG's 3:1 for a focus indicator). It's a 2px inner edge in the full accent glow now.
- Content now reflows into a 320px screen (400% zoom) and survives a doubled browser text size (WCAG 1.4.10, 1.4.4), across
all components.Select's 180px floor,NumberField's readout,SegmentedandStepsas flex items,EmptyState's grid columns, andAvatar's fixed-pixel disc all gave way;Panelcounts its padding inside a width you set (box-sizing: border-box), so a<Panel style={{ width: 220 }}>is now 220px wide overall rather than 260px.
echo-segmentedlost a segment when two shared a label. Segments were keyed by their label, so a control with a repeated label —["Draft", "Sent", "Draft"]— had two React children with the same key: changing the options left a stale segment in the DOM, and Home and End read its index. They are keyed by position now, which is what a segment'svaluealready is.
echo-ui init, run a second time, emptiedecho-lock.json. Changing the theme's seed is documented as runninginitagain, and that threw away the record of which files Echo wrote — so the nextaddread every installed file as one you had edited and refused to write anything.
- A dependency range the CLI could not read stopped
add. A project declaring"echo-ui-core": "^1"(orworkspace:*, orlatest) either failed everyaddwithNot a version: 1, or skipped the core-version check silently and wrote the empty string intoecho-lock.jsonas the installed core version.
- On Windows, an item whose dependencies carry a comparator range failed to install. The install ran through cmd.exe with the command line unquoted, so the
<in>=1.6.0 <2.0.0was a redirect. The line is quoted now, and a spec that is not a package name and a range is refused before it is built.
--jsonwith no command said{ "ok": true }while exiting 2. A missing command is now a refusal on stderr like any other;--helpis still a result and still exits 0.
0.1.0 — 2026-09-11
First public release.
Added
- Theme engine (
echo-theme) — every Layer-3--echo-*role derived in OKLCH from a ~3-value seed, a 7-stop accent ramp, dual{ css, values }output, and WCAG-AA contrast guardrails that solve each text role against every ground it is used on and report seeds they cannot satisfy. - Sound engine (
echo-sound) — sound tokens as serializable synth recipes over a zero-dependency WebAudio backend. No audio assets. Lazy AudioContext, persisted mute, blur guard, reduced-motion coupling. - Motion (
echo-motion,use-tactile,use-gsap-presence) — GSAP motion tokens with reduced-motion collapses, one hook that fuses motion and sound per interaction phase, and a bridge that lets a GSAP exit finish before Base UI unmounts a popup. - 45 components across controls, overlays, structure, surfaces, primitives, navigation and texture — including
Command(a dot-matrix command palette),AppShell,Sheet(on Base UI's Drawer, with swipe-to-dismiss),AlertDialog,Menu+ContextMenu,Textarea, andChart. - Templates as installable blocks —
echo-console. - An agent layer —
catalog.json,llms.txt,llms-full.txt, and an MCP server exposingsearch_components,get_component,get_tokens,get_examples. Props, examples and anti-patterns are generated from each item's zod schema, so the docs and the agent read one source. - Docs site — a live theme generator, a sound audition bench, per-component pages with generated prop tables, ten composed galleries, and a running page per template.
Known limitations
- Dark-only by design. The emissive half of the material language does not survive a light chassis; a light theme needs a separate on-screen text role.
- No OTP field. A one-time code is a mono
Field. A six-digit code wants per-digit boxes and paste-splitting. - Base UI focus guards trip
aria-hidden-focuson any open non-dialog popup (e.g.Select). That markup is upstream and not Echo's to fix; re-checked on each Base UI bump. - Sound recipes are unturned by ear. They are plausible defaults; the audition bench at
/auditionexists to replace them with chosen values.