Registries
Install component source from the official registry, pin it to a commit, and publish a registry of your own
The remix CLI copies component source into your application and leaves you
owning it. A registry is where that source comes from. This guide covers
using the official one, and publishing your own.
The model
Six words carry the whole system.
| term | what it is |
|---|---|
| Registry | A public GitHub repository holding a directory of component source. |
| Namespace | The name your project gives a registry, like @remix. Commands and dependencies use it to say which registry they mean. |
| Preset | One design language inside a registry — vanilla or fortal officially. A project picks exactly one. |
| Catalog | The registry.yaml for one preset. It lists items. |
| Item | One installable unit: its templates, its dependencies, what it exports. |
| Pin | The exact commit your project reads from, recorded in remix.yaml. |
Two rules follow from this and explain most of the CLI's behavior:
- Every read goes through the pin. Never a branch, never a tag — the commit
SHA recorded in your
remix.yaml. Therefrecorded next to it is resolved again only when you runregistry update. Moving to newer source is an explicit act. - A network failure never falls back to bundled content. If the registry is unreachable, the command fails and writes nothing.
Install from the official registry
dart run remix_cli:remix init
dart run remix_cli:remix add buttoninit resolves the registry-stable branch of btwld/remix and stores
its full commit SHA. It checks that your preset exists in that branch and
writes remix.yaml. If the branch is not published, initialization fails and
creates no files.
Omitting --preset selects vanilla. Pass --preset fortal for Fortal, and
--prefix/--ui-path to control naming and location:
dart run remix_cli:remix init --prefix Acme --preset fortal --ui-path lib/design_systemThe result:
schema: 3
prefix: Acme
preset: fortal
paths:
ui: lib/design_system
defaultRegistry: "@remix"
registries:
"@remix":
repository: "btwld/remix"
path: "registry"
ref: "registry-stable"
revision: "<full commit sha>"Commit remix.yaml. There is no separate lockfile.
init registers the official registry under the namespace @remix and makes
it defaultRegistry, so bare names like add button install from it. Keep
that name: other registries can depend on official items as @remix/theme,
and those dependencies resolve through your project's @remix.
add resolves the item's full dependency graph, renders every template, checks
your pubspec.yaml, and only then writes. Your edits to already-installed
files are preserved.
dart run remix_cli:remix add button --dry-run # print the plan, write nothing
dart run remix_cli:remix add button --diff # show what would change
dart run remix_cli:remix add button --overwrite # replace just this item--overwrite replaces only the item you name. It never touches a dependency
you have customized.
Move to newer source
Changing a pin and installing source are separate steps, on purpose.
dart run remix_cli:remix registry update @remix
dart run remix_cli:remix add button --diff
dart run remix_cli:remix add button --overwriteregistry update resolves the recorded ref again. For the official registry
that is registry-stable, so it moves the pin to the newest promoted commit.
Pass --ref to choose something else:
dart run remix_cli:remix registry update @remix --ref <sha> # pin that exact commit
dart run remix_cli:remix registry update @remix --ref registry-stable # follow the branch againA pin recorded with a SHA or a tag resolves to the same commit every time, so a
bare registry update leaves it where it is. Pass --ref to move it.
registry update validates the new pin and rewrites remix.yaml. It does not
touch a single installed file. You then review with --diff and adopt with
--overwrite, item by item.
Rolling back a pin is git checkout on remix.yaml. That restores the pin,
not your source — installed files are yours, so review and restore them through
your own version control.
Projects from an earlier prerelease
remix.yaml schema 3 is the only shape this CLI reads. Schemas 1 and 2 shipped
in earlier prereleases and named no registry at all — they read from a snapshot
that once shipped inside the CLI, and that snapshot is gone. There is nothing to
migrate a pin from, so such a project is reinitialized:
rm remix.yaml
dart run remix_cli:remix init --prefix Acme --preset vanilla
dart run remix_cli:remix add button --diffPass the --prefix, --preset and --ui-path the old file recorded; init
writes the new configuration and leaves everything else alone. Your installed
source and generated adapters are yours and stay where they are, which is why
the --diff on the third line is the part that matters: it is what tells you
whether the source you own still matches the registry you just pinned.
Publish your own registry
A registry is a directory in a public GitHub repository. Nothing else — no
service to run, no account, no publishing step beyond git push.
Layout
registry/
index.yaml # presets -> catalog paths
vanilla/
registry.yaml # the catalog
templates/
button/button.dart.tmplindex.yaml is schema 1 and maps each preset name to its catalog:
schema: 1
presets:
vanilla: vanilla/registry.yamlNamespace and preset are different things
This trips people up, so it is worth being precise:
- A namespace (
@acme) identifies your registry inside a consuming project. Your registry files do not declare it; the consumer types it inregistry add. Pick one and tell consumers to use it, because any catalog that depends on your items names it too. - A preset (
vanilla) identifies a design language. The consuming project selects exactly one, atremix init, and it stays fixed.
remix registry add validates your registry against the preset the project
already selected. So every registry a project uses must offer that
project's preset. Publishing only a acme preset means no project that
initialized against the official registry can add you — remix init always
resolves the official registry, which has no acme.
Until a preset can be introduced by a third-party registry, publish under a
preset name the consuming projects already use — vanilla here. Your
namespace is what keeps your items distinct, not your preset name.
The catalog
registry.yaml is schema 2. Each key under items is an item name, matching
^[a-z][a-z0-9_]*$:
schema: 2
items:
theme:
files:
- source: templates/theme/tokens.dart.tmpl
target: "@ui/theme/tokens.dart"
exports:
- theme/tokens.dart
button:
registryDependencies:
- theme
dependencies:
mix_annotations: ^2.2.0-beta.1
devDependencies:
build_runner: ^2.10.1
mix_generator: ^2.2.0-beta.3
files:
- source: templates/button/button.dart.tmpl
target: "@ui/components/button.dart"
generated:
- "@ui/components/button.g.dart"
exports:
- components/button.dart| field | required | meaning |
|---|---|---|
files | yes | Template source to installed target. |
registryDependencies | no | Other items to install first. |
dependencies | no | Pub constraints the installed source needs at runtime. |
devDependencies | no | Pub constraints needed only to build. |
generated | no | Files the consumer's build_runner will produce. |
exports | no | Paths added to the managed UI barrel. |
Path rules the CLI enforces before writing anything:
sourceis relative and starts withtemplates/.targetstarts with@ui/, which stands in for the consumer's configuredpaths.ui. Storing targets this way is what lets one registry serve every project regardless of where it installs.exportsare relative to that same UI directory.- No traversal, no absolute paths, no URI escapes, in any of them.
Templates
A template is ordinary Dart with two placeholders:
| placeholder | becomes | example |
|---|---|---|
{{typePrefix}} | the project's prefix | AcmeButton |
{{valuePrefix}} | its lower-camel form | acmeButtonStyle |
Any other {{...}} token is rejected — an unresolved placeholder is a
registry bug, not something to install.
Two invariants worth knowing before you design items
No two items may own the same target. If two components need the same file, that file is a third item both depend on. The CLI rejects overlapping ownership before it writes, across every registry in the graph.
No item may own @ui/ui.dart. That barrel is managed by the CLI, assembled
from every installed item's exports.
Depending on another registry
Within a schema-2 catalog, a dependency may name another registry:
registryDependencies:
- theme
- "@company/icons"A bare name means the owning registry's own item. A qualified name means a registry the consuming project has configured. An item cannot register a registry or redirect one — the consumer stays in control of every source their code is installed from.
Publish and consume
Commit the registry/ directory and push it to the ref your consumers follow.
A consumer with a vanilla project then registers you and installs:
dart run remix_cli:remix init # selects vanilla
dart run remix_cli:remix registry add @acme \
--repository your-org/your-repo \
--path registry \
--ref stable
dart run remix_cli:remix add @acme/buttonregistry add resolves the ref to a commit and records both. Without --ref
it resolves the repository's default branch once and records its name and
commit. Namespaces match ^@[a-z][a-z0-9_-]*$.
Bare item names resolve through defaultRegistry; @acme/button names yours
explicitly.
Choose the ref consumers follow
The ref a consumer registers decides what a later bare registry update does:
| consumer registered | registry update @acme | to move on |
|---|---|---|
a branch, such as stable | moves to the branch's newest commit | nothing extra |
a tag, such as v1 | resolves the same commit again | --ref v2 |
| a commit SHA | resolves the same commit again | --ref <newer sha> |
The official registry follows a branch, registry-stable, that CI moves only
after a commit passes its checks. Do the same: keep a branch you advance only
after testing a commit, and tell consumers to register that branch. Tags still
work as labels, and for consumers who only want to move when they choose a
version.
Test a change before consumers see it
The CLI reads registries from GitHub only, never from a local path. Push the change to a working branch, then install it in a scratch app initialized with the preset your consumers use:
dart run remix_cli:remix registry add @acme \
--repository your-org/your-repo \
--path registry \
--ref my-change
dart run remix_cli:remix add @acme/button --dry-run
dart run remix_cli:remix add @acme/button--dry-run resolves the whole dependency graph and renders every template
without writing, so catalog and template errors surface there. The real add
then installs the source and its package dependencies; build the app to confirm
it compiles. Push fixes to the same branch, run registry update @acme, and
reinstall with add @acme/button --overwrite. Advance the branch consumers
follow once the scratch app is clean.
What is not supported
Public github.com repositories only. Private repositories, GitHub Enterprise,
arbitrary HTTP endpoints, and local paths are outside the current release.
There is no persistent offline cache.
Troubleshooting
| message | cause |
|---|---|
No registry-stable branch is published for btwld/remix. | The official registry has not been promoted yet, so init has no commit to pin. Nothing was written; retry once it is published. |
GitHub ref <ref> not found | The branch, tag or commit does not exist in that repository. Check the --ref value. |
GitHub repository not found | The --repository is misspelled, or the repository is private. |
Registry <repo> has no <preset> preset. | The registry's index.yaml does not list your project's preset. |
<a> and <b> both target <path>. | Two items claim the same installed file. Extract it into a shared item. |
Unknown registry @ns. | A dependency names a registry the project has not configured. Add it with registry add. |
Registry dependency cycle: ... | Items depend on each other in a loop. |
GitHub rate limit exceeded | Unauthenticated GitHub API limits are per IP; shared CI runners hit them. Retry after the reset. |
Registry constraints for <pkg> do not intersect. | Two items in one graph require incompatible versions of the same package. |
<item> is partially installed | Some of an item's files exist and some do not. Use --diff or --overwrite. |
Reference
- Open Code — what the CLI installs and why you own it.
remix.yamlschema 3,index.yamlschema 1, and catalog schema 2 are the stable contract. No otherremix.yamlschema is readable.