Kosmesis
Guide

Custom Registry

Serve your own components through kosmesis add, the same way the public Kosmesis registry does.

A registry is just a folder of JSON files — one <name>.json per component, split under a <styleSystem>/ subfolder. kosmesis add reads it from a local directory or an http(s):// URL; there's no server, database, or account required. This is for an internal design system, a client's own variant of a Kosmesis component, or trying out a change before publishing it anywhere.

The CLI has three commands for this — you never have to hand-write the inlined JSON kosmesis add fetches, or the script that builds it:

CommandWhat it does
kosmesis registry init [dir]Scaffolds a registry.json index plus one example component in [dir] (default: here).
kosmesis registry build [dir]Builds registry.json (or registry.<styleSystem>.json) in [dir] into dist/r/<styleSystem>/<name>.json.
kosmesis registry add <namespace> <url>Registers the built output (a local path or a deployed URL) under @<namespace> in a consumer project's components.json.

Neither init nor build needs an existing components.json — they operate on the registry you're authoring, not on a Kosmesis app. add/remove/list do need one, since that's where a consumer project records which registries it trusts.

Quickstart: your first component, end to end

Scaffold it

kosmesis registry init my-registry

This writes:

my-registry/
  registry.json           # the index — one entry per component
  src/
    example-button.tsx    # a real, working example component

registry.json's one entry points at src/example-button.tsx with an empty dependencies / devDependencies / registryDependencies. Edit the example in place, or add more items to registry.json plus their source files under src/ — same shape either way.

Build it

cd my-registry
kosmesis registry build

kosmesis add doesn't read registry.json directly — it fetches <registryBase>/<styleSystem>/<name>.json, one file per component, with the source already inlined as a string. registry build reads registry.json, inlines every referenced file's content, and writes exactly that shape to dist/r/tailwind/example-button.json (default style system is tailwind; pass --style-system praxisjs-css for the other one, or write both registry.tailwind.json and registry.praxisjs-css.json to build both in one call). It fails loudly — naming the exact component — if a files[].path doesn't exist or a registryDependencies name isn't in the index.

Point a project at it

From inside any Kosmesis project (one that already ran kosmesis init):

kosmesis registry add acme ./path/to/my-registry/dist/r
kosmesis add @acme/example-button

src/components/ui/example-button.tsx shows up, written straight from your local folder — no publishing, no network. Run kosmesis registry list any time to see what's registered.

That's the whole loop. Everything below is reference material for scaling this up: serving it over HTTP, style-system-specific dependencies, cross-component dependencies, and the exact JSON shape.

kosmesis registry build reference

FlagDefaultMeaning
[dir] (positional)current directoryWhere to look for registry.json / registry.<styleSystem>.json, and where source files[].path entries are resolved from.
--style-system <tailwind|praxisjs-css>tailwindOnly applies to a bare registry.json (no style-system suffix). Ignored if registry.tailwind.json and/or registry.praxisjs-css.json exist — both get built automatically when present.
--out <dir><dir>/dist/rWhere the built <styleSystem>/<name>.json files are written.

Serving it for real

Once the build output is worth sharing beyond your own machine, upload it to any static file host (S3 behind a CDN, GitHub Pages, an internal file server — anything that serves plain files over HTTP) and point teammates at the URL instead of a local path:

kosmesis registry add acme https://ui.acme.internal/r

Nothing else changes — kosmesis add @acme/example-button resolves the same way whether registries["@acme"] is a path on disk or a URL. This repo's own docs/public/r (built by pnpm registry:build, the same idea as kosmesis registry build but wired into this monorepo's own scripts) is exercised as a local directory the same way before it's ever deployed to kosmesis.praxisjs.org.

Whichever registry resolves a component is trusted at install time: its file contents are written to disk as-is, and its dependencies are installed with your package manager automatically. Only run kosmesis registry add (or pass --registry) for a registry you control or trust — the same bar you'd hold an npm registry mirror to.

Managing registries

CommandWhat it does
kosmesis registry add <namespace> <url>Saves <url> as @<namespace> in components.json. <namespace> can be typed with or without the leading @.
kosmesis registry listPrints the default registry plus every configured namespace.
kosmesis registry remove <namespace>Forgets a namespace.
kosmesis add @<namespace>/<component>Resolves <component> from that namespace instead of the project's default registry.
kosmesis add <component> --registry <url>One-off override of the default registry for a single add call — doesn't touch components.json and doesn't affect namespaced addresses.

A project can mix addresses freely in one call: kosmesis add button @acme/example-button installs button from the default registry and example-button from @acme in the same run.

Dependencies: other components, npm packages, and dev-only npm packages

If one of your components imports another one from the same registry, list it under registryDependencies instead of copying its source in — kosmesis add resolves the full closure and writes every dependency's files too. Add a second item to the registry.json kosmesis registry init scaffolded, alongside example-button:

registry.json
{
  "name": "example-card",
  "registryDependencies": ["example-button"],
  "files": [{ "path": "src/example-card.tsx", "type": "registry:ui" }]
}

kosmesis registry build checks this at build time — it fails loudly, naming the exact component, if registryDependencies points at a name that isn't also in the index.

registryDependencies only resolves within the same registry/namespace — there's no cross-registry lookup. If your component depends on something from the public Kosmesis registry, either vendor that component's source into your own registry, or note it in your docs so consumers run a second kosmesis add for it.

npm packages (as opposed to other registry components) go under dependencies or devDependencies instead, and are installed automatically with the consumer's detected package manager — dependencies ships with the consumer's app, devDependencies is for build/type-check-only packages (type declarations, codegen tools) and gets installed with -D/--save-dev:

{
  "dependencies": ["some-npm-package"],
  "devDependencies": ["@types/some-npm-package"]
}

If a package ends up listed as a runtime dependencies entry by one component and a devDependencies entry by another (in the same kosmesis add call), the runtime one wins — it needs to ship, not just type-check, so it's installed once, without -D.

The full JSON shape

For reference, here's every field a built <styleSystem>/<name>.json can have — this is what kosmesis registry build produces from a registry.json entry, not something you write by hand:

dist/r/tailwind/example-button.json
{
  "name": "example-button",
  "type": "registry:ui",
  "title": "Example Button",
  "description": "Optional, human-readable.",
  "dependencies": ["some-npm-package"],
  "devDependencies": ["@types/some-npm-package"],
  "registryDependencies": ["some-other-component"],
  "files": [
    {
      "path": "src/example-button.tsx",
      "type": "registry:ui",
      "target": "example-button.tsx",
      "content": "// inlined file source"
    }
  ]
}
FieldMeaning
nameMust match the filename. What consumers type after kosmesis add (or @namespace/).
typeregistry:ui, registry:lib, registry:hook, or registry:block.
dependenciesnpm packages needed at runtime, installed automatically.
devDependenciesnpm packages only needed at build/type-check time, installed automatically with -D.
registryDependenciesOther component names in this same registry, resolved and written first.
files[].targetWhere the file lands under the consumer's aliases.ui directory. Defaults to the basename of path.
files[].contentThe full file source, inlined as a string — this is what actually gets written to disk.

On this page