opsinjs
IntroductionStart hereInstallation

Manual installation

No CLI. You place the tokens, the utility, and one component by hand, so you can see exactly what lands.

Overview

There is nothing magic in the CLI. It fetches a JSON registry item, writes the files it names into the directories your components.json aliases point at, and installs any real dependencies. Doing it by hand is entirely reasonable. It is the right choice in three situations: your platform forbids fetching code at build time, you want to review every byte before it enters the repository, or your directory layout is unusual enough that fighting the aliases costs more than copying four files.

Shipped, and no published host yet

The sixty implemented components are real code, and a registry item that is being served carries the whole of the source you would otherwise copy by hand. What is missing is the host: opsinjs.pensievelabs.org does not resolve, so fetching a /r/<name>.json from it fails, and there is no npm package to fall back to. Point the URLs below at a host that is serving the items and the shape is the same. Troubleshooting.

Put the four pieces in place

The token layer

One CSS file defining every opsinjs custom property: the two colour axes, the material ladder, the motion springs, and the type, space and shape scales. Save it as app/opsinjs.css or wherever else your stylesheet lives, and import it after Tailwind and before your own overrides.

app/globals.css
@import "tailwindcss";
@import "./opsinjs.css";

@source "./components/opsinjs/**/*.{ts,tsx}";

This file is generated from the token source and should be replaced wholesale rather than edited. Extensions go in your own @theme block afterwards; see Adding your own tokens.

The cn utility

Copied components import a class-merging helper. If you already have shadcn/ui you already have it at lib/utils.ts and nothing needs doing. If not, it is the standard clsx plus tailwind-merge composition, and it must live at the path your imports expect.

The component source

Fetch the registry item for the component you want and write out each file it names. A registry item is a JSON document listing files, their target paths, their type, and any real dependencies. Nothing is hidden in it, which is the point of publishing the format. registry-item.json annotates every field.

The dependencies

Install whatever the registry item's dependencies array names. For most opsinjs components this is nothing beyond what you already have. Nothing is installed that the component does not genuinely need at runtime.

Fix the import paths

This is the only part that is fiddly by hand. Copied source uses the alias conventions from components.json, such as @/components/... and @/lib/utils. If you are not using those aliases, rewrite the imports as you paste.

Rewriting imports is preferable to inventing a matching alias you will not use elsewhere, and if you are doing this more than twice it is a strong sign that adopting the alias convention would be cheaper than the ongoing edit.

Verify it worked

  1. The component compiles and its imports resolve. Check cn in particular.
  2. It renders styled. Unstyled means Tailwind is not scanning the directory you pasted into.
  3. Its colours come from custom properties. Literals mean the token layer is missing or in the wrong position.
  4. Adding dark to the root element inverts the theme with no JavaScript.
  5. You have recorded what you copied and when, in your own commit message, because the file will not record it for you. Commit the files shadcn add writes in their own commit, so a later --diff has something to compare against.

Troubleshooting

Unstyled component. The @source line does not cover where you pasted it.

cn is not exported. The utility is missing or at a different path from the import.

Wrong colours. Import position: the token layer must come after Tailwind.

You edited the token layer directly and now an update conflicts. Expected. Treat that file as generated: replace it, and keep your changes in your own @theme block afterwards.

You are doing this for every component. Reconsider the CLI. Manual placement is a reasonable answer for one or two components and an expensive habit for twenty.

Next

On this page