个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证A source-first React UI library for SaaS and AI products. 46 components, 8 blocks, owned as code in your repo.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Source-first React components for SaaS and AI products
You install the code, not a dependency.
A dowel is the hidden pin that joins two pieces of wood without a visible
fastener. That is the idea here: components that fit together, hold, and then
get out of your way.
Or add it to a project you already have:
That second command writes button.tsx into your project. Open it. Change it.
It is your file now — there is no package standing between you and the markup.查看完整 README ↗You install the code, not a dependency.
Documentation · Components · CLI
A dowel is the hidden pin that joins two pieces of wood without a visible fastener. That is the idea here: components that fit together, hold, and then get out of your way.
npx create-dowel-app my-app
Or add it to a project you already have:
npx @dowel-ui/cli init
npx @dowel-ui/cli add button
That second command writes button.tsx into your project. Open it. Change it.
It is your file now — there is no package standing between you and the markup.
Live: dowel-eight.vercel.app — docs, every
component, and the registry the CLI reads. Also a
playground, a
theme studio that checks contrast
as you pick, and per-component quality.
Published: @dowel-ui/react, @dowel-ui/cli, @dowel-ui/themes,
@dowel-ui/registry, @dowel-ui/mcp, create-dowel-app.
281 components and 59 blocks, every one keyboard-operable and audited for contrast in light and dark.
AI — Conversation · Message · Response · Prompt Input · Tool Call · Reasoning · Sources · Model Selector · Token Usage · Agent Status · Agent Plan · Approval Request · Action Ledger · Structured Output · Inline Completion · Disclosure · Extraction Review · Suggested Value · Agent Surface · Control Baton · Agent Approvals · Agent Ledger · Agent Form · Agent Data Table · Agent Replay · Blast Radius · Suggest Mode · Prompt Redactor · Stream Announcer
Forms — Input · Label · Checkbox · Radio Group · Switch · Slider · Select · Combobox · Form · Calendar · Date Picker · Tags Input · File Upload · Textarea · Time Range Picker · Cron Editor · Secret Field · Confirm Typed · Shortcut Recorder
Overlays — Dialog · Sheet · Drawer · Popover · Tooltip · Dropdown Menu
Data — Table · Data Table · Accordion · Activity Feed · Code Block · Metric Delta · Record Diff · Log Viewer · Diff Viewer · Permission Matrix · DNS Record
Feedback — Alert · Toast · Progress · Meter · Skeleton · Spinner · Empty State · Sync Status · Session Expiry
Navigation & layout — Tabs · Command · Pagination · Breadcrumb · Sidebar · Collapsible · Card · Badge · Avatar · Button · Separator · Direction Provider
Most AI component sets ship a chat transcript and stop. Real AI features inside real software are extraction, enrichment, autofill, and agents that change things — which need surfaces nobody else provides.
ai-action-ledger — what the agent actually did, and what can be undone.
Everyone ships approval before a tool runs; nothing ships the part after,
where a deletion can be reverted, a refund can only be offset by another
transaction, and a sent email cannot be taken back at all.
ai-structured-output — an object arriving field by field, layout reserved
up front so nothing jumps as it fills in.
ai-inline-completion — ghost text in a real textarea. Escape always gives
Tab back to focus management, so a keyboard user is never trapped.
agent-surface + control-baton — the agent operates the page, and the
person can take it back. Actions register as tools that run the same handlers a
click does, for your own assistant or, experimentally, for browser agents over
WebMCP. While the person holds control every call is refused, reads included,
and handing back carries a note the agent receives with its next result. Around it: agent-approvals (correct the
arguments, then approve), agent-ledger (undo what can be undone), and
ready-made tools for forms (agent-form) and TanStack tables
(agent-data-table), blast-radius (what approving will change),
agent-replay (how the run went, step by step) and ai-suggest-mode (the
agent proposes edits, the person accepts each one).
prompt-redactor — emails, card numbers and keys swapped for placeholders
before a prompt is sent, and put back in the reply.
stream-announcer — an opt-in way for screen reader users to hear a
streaming reply a sentence at a time, with pause, skip and repeat.
permission-prompt + memory-inspector — what an assistant may do and
what it knows. One asks for a capability when it is needed (allow once, for the
session, always, or not at all); the other shows what is remembered about the
person, where it came from, and lets them edit or forget it.
provenance-text — who wrote each part of a paragraph: the person, an
agent or a quoted source, marked by more than colour and read out to screen
readers.
chart-sonifier — hear a chart: each series as pitch over time, and a
keyboard slider that speaks every value.
Also new: nl-filter (words in, editable filter chips out), expression-editor
(formulas with autocomplete and a safe evaluator) and quantity-input (a
number with a unit that converts).
Whole sections, assembled from components. Installing one brings everything it
is built from — add ai-chat resolves fourteen components.
Auth — Login · Sign up · Forgot password
SaaS — Dashboard · Analytics · Billing · Onboarding · Admin users · Settings · Pricing
AI — AI Chat · AI Dashboard · Agent Console
Pro — CRM · Command Center · AI Workspace · Admin Dashboard
billing states every usage meter in words rather than as a bar that turns red.
analytics treats its chart as a picture — one summarising label, and the exact
numbers as a real table anyone can open. ai-dashboard marks spend and failure
rate as lower-is-better, because a dashboard that paints a rising bill green is
congratulating you on it. agent-console puts whatever the run is blocked on
above the plan and the history, since it is the only part waiting on a person.
The four Pro blocks are whole application surfaces rather than page sections:
crm is a pipeline with its deals, command-center is service health,
incidents, capacity, logs and a ⌘K palette, ai-workspace is a three-column
AI application with the model's context beside the conversation, and
admin-dashboard is the shell an admin area shares plus its front page. They
install with the same command once the CLI is signed in — see
Pro — and their previews on the site are the real thing.
import {
Button,
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
Input,
} from "@dowel-ui/react";
<Card>
<CardHeader>
<CardTitle>Create project</CardTitle>
</CardHeader>
<CardContent>
<Input placeholder="Project name" />
</CardContent>
<CardFooter>
<Button>Create project</Button>
</CardFooter>
</Card>;
Form wires field accessibility without binding to a form library. Pass error
from wherever your state lives — useState, React Hook Form, a server action —
and the markup is identical:
<FormField name="email" error={errors.email?.message}>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...register("email")} />
</FormControl>
<FormDescription>We will never share it.</FormDescription>
<FormMessage />
</FormField>
Toast has an imperative API, callable from anywhere — no hook, no provider
lookup. Render <Toaster /> once near the root:
import { toast } from "@dowel-ui/react";
toast.success("Project created", { description: "It is ready to use." });
await toast.promise(save(), {
loading: "Saving…",
success: (name) => `Saved ${name}`,
error: "Could not save",
});
Set a project up once, then add components as source you own:
npx @dowel-ui/cli init
npx @dowel-ui/cli add button card dialog
add pulls in whatever a component depends on — add date-picker also installs
Calendar, Popover, Button and Spinner — installs the npm packages it needs, and
rewrites imports to your project's own path alias.
| Command | Does |
|---|---|
init | Writes components.json, the cn() utility and the design tokens |
add <names…> | Installs components and everything they depend on |
list | Shows the registry, marking what you already have |
update | Compares installed components against the registry |
plan | Chooses the components for a screen you describe |
diff | Shows how installed files differ from the registry's |
doctor | Checks the setup, installed components and agent docs |
audit | Finds hardcoded colours, off-scale sizes and bypassed components |
agents | Writes the catalogue for the coding agents in this project |
login | Stores a licence key, for components that need one |
whoami | Reports whether this machine is signed in |
Useful flags: --registry <url-or-directory> to point at a fork or mirror,
--yes for CI, --overwrite to replace files you have edited, --cwd to run
against another directory.
Re-running add is safe. A file you have not touched is left alone because
it already matches; a file you have edited is never overwritten without
--overwrite, which is the whole point of owning the source. This works because
add records a hash of what it wrote, so update can tell your changes apart
from upstream ones.
doctor and audit are for after the install. doctor checks the
project: Tailwind 4 and the alias, the tokens in the stylesheet, every installed
file still present, the npm packages they import, updates available, and
whether the agent docs are stale. It prints a checklist rather than a score,
and exits non-zero only on a failure. audit reads the project's own code for
the ways a design system erodes: Tailwind palette colours and literal colours
in classes or inline styles, arbitrary sizes off the spacing scale, physical
ml-/text-left utilities that break right-to-left, and native <button>,
<input> or <dialog> elements where the Dowel component is installed. It
skips the files Dowel wrote, uses the same rules as the library's own audits,
and --fix rewrites only the findings with one exact fix, after asking.
The CLI refuses, loudly, to install into a project it cannot support correctly:
Tailwind v3 (the tokens use @theme), a JavaScript project (the published
source is TypeScript), or a non-React project. Each refusal says what to do
instead.
npx create-dowel-app my-app
Asks what you are building, and which theme, then writes a Next.js app and fetches the components for it.
| Template | What you get |
|---|---|
starter | The app, the tokens, the aliases, and a landing page |
saas | An application shell with dashboard, analytics, billing, settings and onboarding |
ai | A chat surface, an agent console and a usage dashboard |
The templates do not contain the components. They are the application files plus a list of registry names, and the scaffolder runs the same CLI you would run yourself. A template carrying its own copy of Button is carrying whichever Button was current the day it was written, and nothing ever says so — this way a project created today is built from today's registry, and a template stays a dozen files instead of a hundred.
Flags: --template, --theme, --pm, --yes, --skip-install.
npx @dowel-ui/cli list # what exists
dowel-eight.vercel.app/generate —
describe a screen, get the components that build it, the install command, and a
brief to paste into your coding agent. The MCP server exposes the same thing as
plan_ui, so an agent can ask for it directly, and so does the CLI:
npx @dowel-ui/cli plan "a support dashboard with ticket filters and an assistant"
npx @dowel-ui/cli plan "…" --model # choose with Claude, using your own credentials
Every suggestion is resolved against the registry before anything is written, so
it cannot name a component that does not exist — which is what asking a model
directly gets you, complete with a variant nobody implemented. That holds with
--model too: the model chooses from the catalogue it is given, with each
item's guidance on when to use it, and anything it names that the registry does
not have is dropped and reported. --model needs @dowel-ui/cli's optional peer
@anthropic-ai/sdk and Anthropic credentials; it sends the prompt and the public
catalogue, never your files.
The plan stops at the composition. It does not write props: each component's props are published on its registry item, read from its type, and the agent is pointed at them — a plausible invented prop is worse than an obvious gap.
Everything above is MIT and stays that way: an item that has ever been installable without a licence never stops being one. Pro is the catalogue of whole application surfaces built on top — currently the four blocks above, with more added to the same licence rather than sold separately.
A licence is a key. Sign in once and the install is the same command:
npx @dowel-ui/cli login
npx @dowel-ui/cli add crm
The key is checked against the registry the moment it is pasted, so a bad key
fails then rather than during an install a week later. It is stored in your own
config directory, never in the project; CI sets DOWEL_TOKEN from its secrets
store instead. The key is only ever sent over HTTPS to the registry it was
issued for, so a repository whose components.json names some other server
cannot collect it. The registry lists every Pro item — title, description, what it
is built from — and serves the source only to a licence holder, so a Pro block
still shows up in list, in the agent docs and in the MCP server, with what it
is and how to get it.
Pricing, and what a licence covers, is on the site: dowel-eight.vercel.app/pricing.
The CLI has always installed from any registry — --registry takes a URL or a
directory. @dowel-ui/registry now builds one, so an organisation can publish
its own components and have them installed exactly the same way.
import { buildCustomRegistry, defineRegistryConfig } from "@dowel-ui/registry";
const result = await buildCustomRegistry(
defineRegistryConfig({
root: "src",
generatedFrom: "@acme/[email protected]",
// One URL that serves both your components and everything upstream.
extends: "https://dowel-eight.vercel.app/r",
items: [
{
name: "acme-callout",
title: "Acme Callout",
description: "Acme's house callout, built on the upstream Badge.",
category: "display",
registryDependencies: ["badge"],
files: ["acme-callout.tsx"],
},
],
}),
);
Point a project at the result and add acme-callout installs it, pulling in
badge from upstream on the way. Put the directory behind anything that answers
a missing key with 401, and login --registry <url> signs a developer in; keys
are stored per registry and only ever sent to their own. Items can name an
owner, the version they shipped since, and a deprecated notice with a
replacement, which the CLI, the MCP server and the agent docs all honour.
A local item replaces an upstream one of the same name, and the build tells you which — overriding upstream's Button is a legitimate thing to want and a catastrophic thing to do by accident, and the difference is whether anyone was told.
The build refuses three things rather than letting them fail in a consumer's repository:
@/components/ui/badge)
rather than the authored one (@/components/badge) — the leading group is
rewritten to wherever the project keeps its components, so naming it twice
produces a path that resolves nowhere;An agent that has never heard of Dowel writes its own Button — a second one, with a different focus ring, different disabled semantics and hardcoded colours, and now the design system has a hole in it that nobody notices until someone tabs into it.
npx @dowel-ui/cli agents
Writes the catalogue and the conventions into your repository, generated from the registry you install from, marking what you already have:
| Writes | For |
|---|---|
.dowel/*.md | Any agent that reads the repository |
AGENTS.md | A marked block; the rest of the file is yours |
.claude/skills/dowel-ui/SKILL.md | Claude Code |
.cursor/rules/dowel-ui.mdc | Cursor |
--check writes nothing, reports what is stale and exits non-zero — a
catalogue a release behind is worse than none, because the agent trusts it.
MCP. The files are a snapshot; @dowel-ui/mcp is the live version. The
agent queries the registry directly, reads a component's real source rather
than a description of it, and is told when it has typed a name that does not
exist.
{
"mcpServers": {
"dowel": {
"command": "npx",
"args": ["-y", "@dowel-ui/mcp"],
"env": { "DOWEL_IMPORT_FROM": "@/components/ui" }
}
}
}
Six tools: search_components, get_component (with when to use it, what it
is confused with, and its props), get_guide, install_command, plan_ui, and
audit_code, which checks what the agent wrote against the same rules as
dowel audit before anyone sees it.
llms.txt. For an agent that can fetch a URL but not run a server, the site
serves /llms.txt (the index) and
/llms-full.txt (everything in
one request), generated at build time from the same registry.
Requires Node ≥ 22.18 and pnpm 11 to develop. The published CLI, MCP server and scaffolder run on Node 20.12 or later, and CI runs them there.
pnpm install
pnpm storybook
| Command | Does |
|---|---|
pnpm storybook | Component development environment on :6006 |
pnpm test | Vitest, including the registry integrity suite |
pnpm lint | ESLint, including jsx-a11y |
pnpm typecheck | tsc --noEmit across every package |
pnpm build | Build all packages |
pnpm check:branding | List every surviving branding placeholder |
pnpm audit:all | Contrast, tokens, API conventions, bundle, npm packaging |
Tokens are two-tier. Tier 1 is raw scales — a cool-tinted OKLCH neutral ramp, a
radius ladder, a 15px-base type scale, elevation, motion. Tier 2 is semantic
aliases (--primary, --border, --ring) that components consume exclusively.
Re-skinning the system means reassigning Tier 2; no component file changes.
Thirteen presets ship: default, ocean, emerald, violet, rose, amber,
monochrome, and six derived from SmoothUI's themes — candy, indigo,
blue, red, orange, green. Apply one with data-theme on <html>; dark
mode is the dark class. A single --radius-scale custom property
re-proportions every corner in the system at once.
monochrome is not only a style — it is a standing check that no component uses
colour as its only signal.
Motion works the same way. --motion-scale is a single multiplier every
duration derives from, so one property re-times the whole system. Under
prefers-reduced-motion it collapses — but indicators that report ongoing
state, like a spinner or a streaming caret, are slowed rather than stopped via
--motion-scale-indicator, because a frozen spinner says the application has
hung.
Targeted at WCAG 2.2 AA. Every component has an axe assertion in its test suite and runs under the Storybook a11y addon with findings set to fail rather than warn. Keyboard interaction is tested, not assumed.
Contrast is checked separately, because a test environment that never paints
cannot check it: audit:contrast converts the OKLCH tokens to sRGB and verifies
all 962 semantic pairs across both modes and all thirteen presets. It runs in CI.
A few choices worth knowing about, because they differ from what similar libraries do:
A loading Button uses aria-disabled + aria-busy and guards its click
handler, rather than the disabled attribute. Disabling a control mid-action
strands the user's keyboard focus.
Alert is not a live region by default. One that exists on first paint
announces for no reason. Opt in with live="polite" or live="assertive".
Separator, Skeleton and Spinner are decorative by default and stay
out of the accessibility tree unless you give them a label.
PopoverContent warns in development when it has no accessible name. It
carries role="dialog", so without aria-label or aria-labelledby it is
announced as an unnamed dialog — a defect that is invisible in the browser.
A gesture is never the only way out. Drawer can be dragged away, but Escape, the overlay and an explicit cancel are always there too.
Slider forwards its name onto the thumbs, because the thumb is the
role="slider" element. Use thumbLabels to name each end of a range
separately — two thumbs called "Price" tell a screen reader user nothing.
Form never names an element that was not rendered. aria-describedby
is assembled from the description and error that actually exist.
Table's scroll wrapper is focusable. A table that overflows its
container is otherwise unreachable by keyboard.
Sortable headers carry aria-sort plus hidden text naming the column and
direction; an arrow icon is no signal for a screen reader.
A streaming transcript is not a live region. Announcing text token by token interrupts a screen reader continuously; state is announced separately and the content is left navigable. This is the most common accessibility failure in chat interfaces.
The composer does not send mid-IME-composition, which otherwise truncates Japanese, Chinese and Korean input mid-word.
Drawer, Toast, Combobox and Command are built on primitives rather than on
vaul, sonner and cmdk, so the runtime footprint stays at radix-ui,
cva, clsx, tailwind-merge, plus react-day-picker for Calendar and
@tanstack/react-table for Data Table. See ADRs
5,
6 and
8.
Decisions that constrain future work are recorded in
docs/architecture/:
The plan these came out of, with what is done and what is not, is the roadmap.
React 19, Tailwind CSS v4 (v3 is not supported — tokens are defined in CSS
via @theme), and a bundler that handles CSS imports.
See CONTRIBUTING.md. Read the component conventions before adding a component; the test suite enforces most of them.