RemixRemix

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.

termwhat it is
RegistryA public GitHub repository holding a directory of component source.
NamespaceThe name your project gives a registry, like @remix. Commands and dependencies use it to say which registry they mean.
PresetOne design language inside a registry — vanilla or fortal officially. A project picks exactly one.
CatalogThe registry.yaml for one preset. It lists items.
ItemOne installable unit: its templates, its dependencies, what it exports.
PinThe 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. The ref recorded next to it is resolved again only when you run registry 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 button

init 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_system

The 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 --overwrite

registry 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 again

A 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 --diff

Pass 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.tmpl

index.yaml is schema 1 and maps each preset name to its catalog:

schema: 1
presets:
  vanilla: vanilla/registry.yaml

Namespace 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 in registry 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, at remix 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
fieldrequiredmeaning
filesyesTemplate source to installed target.
registryDependenciesnoOther items to install first.
dependenciesnoPub constraints the installed source needs at runtime.
devDependenciesnoPub constraints needed only to build.
generatednoFiles the consumer's build_runner will produce.
exportsnoPaths added to the managed UI barrel.

Path rules the CLI enforces before writing anything:

  • source is relative and starts with templates/.
  • target starts with @ui/, which stands in for the consumer's configured paths.ui. Storing targets this way is what lets one registry serve every project regardless of where it installs.
  • exports are 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:

placeholderbecomesexample
{{typePrefix}}the project's prefixAcmeButton
{{valuePrefix}}its lower-camel formacmeButtonStyle

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/button

registry 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 registeredregistry update @acmeto move on
a branch, such as stablemoves to the branch's newest commitnothing extra
a tag, such as v1resolves the same commit again--ref v2
a commit SHAresolves 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

messagecause
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 foundThe branch, tag or commit does not exist in that repository. Check the --ref value.
GitHub repository not foundThe --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 exceededUnauthenticated 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 installedSome 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.yaml schema 3, index.yaml schema 1, and catalog schema 2 are the stable contract. No other remix.yaml schema is readable.
View page source on GitHub

On this page