An open API service indexing awesome lists of open source software.

https://github.com/low-orbit-studio/visor


https://github.com/low-orbit-studio/visor

component-library css-modules design-system nextjs oklch react shadcn-style theming visor

Last synced: 9 days ago
JSON representation

Awesome Lists containing this project

README

          


Visor — One component system. Total Control. By Low Orbit Studio


visor-core version
CLI version
theme-engine version
CI
License
npm downloads
bundle size
Documentation

---

## What is Visor?

Visor is a theming-first React component library built by [Low Orbit Studio](https://loworbit.studio). It uses a two-layer distribution model that gives you full control over your components while keeping design consistency effortless:

**Layer 1 — Components (copy-and-own).** Run `npx visor add button` and the source files are copied directly into your project. You own them. Edit them freely. No runtime dependency on Visor.

**Layer 2 — Tokens (`@loworbitstudio/visor-core`).** The only npm package. It provides all the CSS custom properties that Visor components reference. Update the package and design changes cascade to every component automatically — without touching a single component file.

This model is inspired by shadcn/ui's copy-and-own approach, combined with a shared token layer that keeps multi-project consistency without locking you in.

---

## Quick Start

### One command. Runnable Next.js app. Borealis-native.

In an empty directory, run:

```sh
npx @loworbitstudio/visor init --template nextjs
```

That single command scaffolds a complete, runnable Next.js App Router project pre-wired with Visor:

- `package.json` with `next`, `react`, TypeScript, and `@loworbitstudio/visor-core` + `@loworbitstudio/visor-theme-engine` already installed.
- `app/layout.tsx` with the FOWT (Flash of Wrong Theme) prevention script inline in `` and `globals.css` imported.
- `app/globals.css` generated from `.visor.yaml` via the Visor Next.js adapter.
- `app/page.tsx`, `tsconfig.json`, `next.config.ts`, `.gitignore` — the full create-next-app baseline.
- `.lo/borealis.json` stamp recording the Visor version that initialized the project.

Then start the dev server:

```sh
cd my-app && npm run dev
```

Add your first component:

```sh
npx visor add button
```

That's it. The component source lands in your project and you own it. No FOWT flash, no missing config, no second setup step.

> **Heads up:** `visor init --template nextjs` only scaffolds into empty directories — it refuses if `package.json` already exists so it never destructively overwrites in-flight work. For an existing app, use the manual setup below.

### Manual setup (non-Next.js or retrofit)

For non-Next.js projects, or to retrofit Visor into an existing app:

**1. Initialize Visor**

```sh
npx @loworbitstudio/visor init
```

This creates a `visor.json` in your project root with default path mappings:

```json
{
"paths": {
"components": "components/ui",
"hooks": "hooks",
"lib": "lib"
}
}
```

**2. Import tokens into your global CSS**

```css
/* app/globals.css or src/index.css */
@import "@loworbitstudio/visor-core";
```

**3. Add your first component**

```sh
npx visor add button
```

---

## Adding Components

Add components one at a time or in bulk:

```sh
npx visor add input
npx visor add card
npx visor add button input label card
```

### Available Components

The registry ships 88+ UI components across 6 categories, plus admin compounds, blocks, and hooks.

**Form (24)**
`button` · `calendar` · `checkbox` · `combobox` · `date-picker` · `field` · `fieldset` · `file-upload` · `form` · `input` · `label` · `number-input` · `otp-input` · `password-input` · `phone-input` · `radio-group` · `search-input` · `select` · `slider` · `slider-control` · `switch` · `tag-input` · `textarea` · `toggle-group`

**Data Display (12)**
`accordion` · `avatar` · `carousel` · `code-block` · `collapsible` · `heading` · `image` · `progress` · `separator` · `skeleton` · `text` · `timeline`

**Navigation (5)**
`breadcrumb` · `command` · `navbar` · `pagination` · `stepper`

**Overlay (7)**
`context-menu` · `dialog` · `fullscreen-overlay` · `hover-card` · `lightbox` · `menubar` · `popover`

**Feedback (6)**
`alert` · `banner` · `chart` · `table` · `toast` · `tooltip`

**Layout (9)**
`badge` · `box` · `card` · `container` · `grid` · `inline` · `sheet` · `sidebar` · `stack`

The five primitives `box`, `container`, `grid`, `inline`, and `stack` are token-typed layout building blocks: all spacing, surface, and radius props accept only Visor token names — off-system values are TypeScript errors. Pair them with `Card`, `Sheet`, and `Sidebar` for full-page chrome.

Add an entire category at once:

```sh
npx visor add --category form # Add all form components
npx visor add --category overlay # Add all overlay components
```

### Admin Components

10 compound components for data-heavy admin UIs. Add with `--category admin`:

```sh
npx visor add --category admin
```

| Component | CLI Name | Description |
|-----------|----------|-------------|
| Activity Feed | `activity-feed` | Timestamped event stream |
| Bulk Action Bar | `bulk-action-bar` | Floating bar for multi-select actions |
| Confirm Dialog | `confirm-dialog` | Destructive action confirmation modal |
| Data Table | `data-table` | Sortable, filterable table with pagination |
| Empty State | `empty-state` | Zero-data placeholder with CTA |
| Filter Bar | `filter-bar` | Composable filter chip row |
| Kbd | `kbd` | Keyboard shortcut display |
| Page Header | `page-header` | Title + actions header for admin pages |
| Stat Card | `stat-card` | KPI metric card with trend |
| Status Badge | `status-badge` | Semantic status indicator |

### Blocks

19 full-page and section-level blocks. Add with `--block`:

```sh
npx visor add admin-dashboard --block
npx visor add hero-section --block
```

| Block | CLI Name | Category |
|-------|----------|----------|
| Admin Dashboard | `admin-dashboard` | Admin |
| Admin Detail Drawer | `admin-detail-drawer` | Admin |
| Admin List Page | `admin-list-page` | Admin |
| Admin Settings Page | `admin-settings-page` | Admin |
| Admin Shell | `admin-shell` | Admin |
| Admin Tabbed Editor | `admin-tabbed-editor` | Admin |
| Admin Wizard | `admin-wizard` | Admin |
| CTA Section | `cta-section` | Marketing |
| Features Grid | `features-grid` | Marketing |
| Footer Section | `footer-section` | Marketing |
| Hero Section | `hero-section` | Marketing |
| Pricing Section | `pricing-section` | Marketing |
| Steps Section | `steps-section` | Marketing |
| Testimonial Section | `testimonial-section` | Marketing |
| Login Form | `login-form` | Auth |
| Configuration Panel | `configuration-panel` | Configuration |
| Design System Deck | `design-system-deck` | Documentation |
| Design System Specimen | `design-system-specimen` | Documentation |
| Sphere Playground | `sphere-playground` | Visual |

### Available Hooks

**General (10)**
`use-boolean` · `use-click-outside` · `use-currency` · `use-debounce` · `use-focus-trap` · `use-intersection-observer` · `use-keyboard-shortcut` · `use-local-storage` · `use-media-query` · `use-previous`

**Deck (4)**
`use-intersection-animation` · `use-keyboard-nav` · `use-slide-engine` · `use-wheel-nav`

```sh
npx visor add use-boolean
npx visor add use-debounce
npx visor add use-slide-engine
```

---

## How It Works

When you run `npx visor add button`, two files land in your project:

```
your-project/
├── components/
│ └── ui/
│ └── button/
│ ├── button.tsx ← React component (yours to edit)
│ └── button.module.css ← Component styles (yours to edit)
└── lib/
└── utils.ts ← cn() helper, added once and shared
```

Components use CSS Modules for scoped class names and CSS custom properties from the tokens package for all design values:

```css
/* button.module.css */
.base {
border-radius: var(--radius-md);
font-size: var(--text-sm);
}

.variantDefault {
background-color: var(--interactive-primary-bg);
color: var(--interactive-primary-text);
}
```

Variants are managed with [CVA](https://cva.style):

```tsx
// button.tsx
const buttonVariants = cva(styles.base, {
variants: {
variant: {
default: styles.variantDefault,
secondary: styles.variantSecondary,
},
size: {
sm: styles.sizeSm,
md: styles.sizeMd,
},
},
defaultVariants: { variant: "default", size: "md" },
})
```

---

## Theming

Theming is Visor's core differentiator. Every component references CSS custom properties — never hard-coded values. Swap the token values and the entire UI follows.

### The 3-Tier Token Architecture

```
Tier 1: Primitives Tier 2: Semantic Tier 3: Adaptive
--color-gray-900 ──→ --text-primary ──→ :root { --text-primary }
--color-gray-50 ──→ --surface-page ──→ .theme-dark { ... }
--radius-lg ──→ --border-default
```

Components only reference Tier 2 (semantic) tokens. This means overriding a single semantic token updates every component that uses it.

### Dark Mode

Visor ships with a dark theme out of the box. Apply it by adding `.theme-dark` to your root element:

```html

```

### Overriding Tokens

Override any token after your `@import` statement — no forking required:

```css
/* globals.css */
@import "@loworbitstudio/visor-core";

:root {
/* Rebrand the primary color across the entire system */
--interactive-primary-bg: #6366f1;
--interactive-primary-bg-hover: #4f46e5;
}

.theme-dark {
--interactive-primary-bg: #818cf8;
}
```

### Creating a Custom Theme

```css
/* styles/theme-brand.css */
.theme-brand {
--surface-page: #0a0a14;
--surface-card: #12121f;
--text-primary: #f0f0ff;
--text-secondary: #a0a0c0;
--interactive-primary-bg: #6366f1;
--interactive-primary-text: #ffffff;
--border-default: rgba(255, 255, 255, 0.1);
}
```

```tsx
// app/layout.tsx
export default function RootLayout({ children }) {
return (

{children}

)
}
```

### Creating a Theme from `.visor.yaml`

Define your theme in a YAML file and generate framework-specific CSS:

```yaml
# .visor.yaml
name: my-brand
version: 1
colors:
primary: "#6366f1"
```

```bash
# Generate Next.js globals.css with @layer support
npx @loworbitstudio/visor theme apply .visor.yaml --adapter nextjs

# Generate fumadocs bridge tokens
npx @loworbitstudio/visor theme apply .visor.yaml --adapter fumadocs

# Generate scoped deck CSS
npx @loworbitstudio/visor theme apply .visor.yaml --adapter deck

# Generate docs-site CSS (class-scoped, includes fumadocs bridge)
npx @loworbitstudio/visor theme apply .visor.yaml --adapter docs
```

Register a theme in the Visor docs site in one command:

```bash
# Creates CSS file, updates globals.css and theme-config.ts
npx @loworbitstudio/visor theme register .visor.yaml --group "Client"

# Preview changes without writing
npx @loworbitstudio/visor theme register .visor.yaml --group "Client" --dry-run

# Remove a theme
npx @loworbitstudio/visor theme unregister my-brand
```

Or scaffold a complete themed project:

```bash
npx @loworbitstudio/visor init --template nextjs
```

### FOWT Prevention

> Already wired automatically when you use `npx @loworbitstudio/visor init --template nextjs`. The steps below are only needed for manual setups or non-Next.js apps.

Prevent flash of wrong theme by adding a blocking script to your ``:

```typescript
import { FOWT_SCRIPT } from '@loworbitstudio/visor-theme-engine/fowt';

// In your layout.tsx :
{FOWT_SCRIPT}
```

### Importing Specific Token Layers

```css
@import "@loworbitstudio/visor-core/primitives"; /* Tier 1: raw values */
@import "@loworbitstudio/visor-core/semantic"; /* Tier 2: purpose-named */
@import "@loworbitstudio/visor-core/themes/light"; /* Tier 3: light theme */
@import "@loworbitstudio/visor-core/themes/dark"; /* Tier 3: dark theme */
```

### CSS Layer Architecture

Visor's distributed CSS uses [CSS Cascade Layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) so generated themes win the cascade without consumer intervention.

Every shipped `dist/*.css` file declares the same layer order and wraps its content in the matching tier:

```css
@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;
```

| Layer | Source | Purpose |
| --- | --- | --- |
| `visor-primitives` | `@loworbitstudio/visor-core/primitives` | Raw token values (colors, spacing, type) |
| `visor-semantic` | `@loworbitstudio/visor-core/semantic` | Purpose-named tokens (`--text-primary`, `--surface-card`) |
| `visor-adaptive` | `@loworbitstudio/visor-core/themes/*`, generated themes (`visor theme apply --adapter nextjs`) | Light/dark-aware tokens, generated theme overrides |
| `visor-bridge` | Framework integrations (e.g. fumadocs) | Maps Visor tokens onto framework-native variables |

**Cascade rules at a glance:**

- Per the CSS spec, **unlayered styles always beat layered styles**. So your bare `:root { ... }` overrides written after `@import "@loworbitstudio/visor-core"` continue to win — that pattern still works as documented above.
- **Generated themes win over visor-core defaults.** Both sit in `@layer visor-adaptive`, and last-loaded wins within a layer, so importing a generated theme after visor-core gives the theme its expected priority.
- **Stock themes ship layered too.** When you import `@loworbitstudio/visor-core/themes/blackout` (or any other stock theme), the `.{slug}-theme` class still wins on selector specificity but its rules participate in `visor-adaptive` so they coexist cleanly with generated themes.

---

## Updating

### Updating a Component

Re-run the CLI with `--overwrite` to pull the latest upstream version:

```sh
npx visor add button --overwrite
```

Because you own the files, the CLI shows a diff before overwriting. If you've customized the component, use git to merge:

1. Commit your customizations.
2. Run `npx visor add button --overwrite`.
3. Use `git diff` to review what changed.
4. Merge your customizations into the updated version.
5. Commit the result.

### Updating Tokens

Token updates are standard npm updates:

```sh
npm update @loworbitstudio/visor-core
```

Token updates propagate automatically to all components. No component files change.

---

## CLI Reference

```sh
# Setup
npx @loworbitstudio/visor init # Create visor.json config
npx @loworbitstudio/visor init --template nextjs # Initialize with Next.js template

# Components
npx @loworbitstudio/visor add # Add a component, hook, or lib entry
npx @loworbitstudio/visor add # Add multiple at once
npx @loworbitstudio/visor add --category # Add all items in a category
npx @loworbitstudio/visor add --block # Add a block
npx @loworbitstudio/visor add --overwrite # Update an existing component
npx @loworbitstudio/visor list # List all available components
npx @loworbitstudio/visor list --category # List by category
npx @loworbitstudio/visor diff [component] # Show local vs. registry differences
npx @loworbitstudio/visor suggest --for "" # Find components for a use case
npx @loworbitstudio/visor suggest --for "" --json # JSON output (for AI agents)

# Themes
npx @loworbitstudio/visor theme apply # Generate CSS from .visor.yaml
npx @loworbitstudio/visor theme apply --adapter nextjs # Next.js adapter
npx @loworbitstudio/visor theme apply --adapter nextjs --scope-prefix 'body.my-theme' # Body-class scoped output
npx @loworbitstudio/visor theme apply --adapter fumadocs # fumadocs adapter
npx @loworbitstudio/visor theme apply --adapter deck # Deck adapter
npx @loworbitstudio/visor theme validate # Validate a .visor.yaml theme
npx @loworbitstudio/visor theme export [file] # Export theme to YAML/JSON
npx @loworbitstudio/visor theme extract # Extract .visor.yaml from existing CSS
npx @loworbitstudio/visor theme register # Register theme in the docs site
npx @loworbitstudio/visor theme unregister # Remove a theme from the docs site
npx @loworbitstudio/visor theme sync # Re-generate CSS for all themes

# Fonts
npx @loworbitstudio/visor fonts add --org # Upload woff2 to Visor Fonts CDN
```

All commands support `--json` for structured output (useful for AI agents and scripts).

---

## AI Agent Consumability

Visor includes structured metadata that makes it easy for AI agents to discover, understand, and compose components without reading source code.

**Per-component metadata** — Each component has a `.visor.yaml` file alongside its source with props, variants, slots, dependencies, usage examples, and "when to use" / "when not to use" guidance.

**Registry manifest** — `visor-manifest.json` is auto-generated during build, aggregating all component metadata (including auto-extracted CSS tokens) into a single file an agent can load.

**Composition patterns** — Pattern files in `patterns/` document how components combine for common use cases (form with validation, dashboard layout, CRUD table).

See [docs/ai-consumability.md](docs/ai-consumability.md) for the full spec.

---

## Stack

- **React + TypeScript**
- **CSS Modules** + CSS custom properties (no Tailwind, no CSS-in-JS)
- **[CVA](https://cva.style)** for variant management
- **[Radix UI](https://radix-ui.com)** for accessible primitives
- **[Phosphor Icons](https://phosphoricons.com)**
- **[Vitest](https://vitest.dev) + [React Testing Library](https://testing-library.com/react)** for testing
- **[fumadocs](https://fumadocs.vercel.app)** for the documentation site

---

## Documentation

Full documentation, component previews, and a props reference are available at:

**[visor.loworbit.studio](https://visor.loworbit.studio)**

---

## Built with Visor

- **[Kaiah](https://github.com/low-orbit-studio/kaiah)** — AI-powered marketing platform
- **[Blacklight](https://github.com/low-orbit-studio/blacklight)** — Music industry intelligence tool

Using Visor in your project? [Open a PR](https://github.com/low-orbit-studio/visor/edit/main/README.md) to add it here.

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on submitting components, token changes, and bug fixes.

To develop locally:

```sh
git clone https://github.com/loworbit/visor.git
cd visor
npm install

npm test # Run tests
npm run typecheck # Type check
npm run lint # Lint
npm run build # Build all packages
npm run docs:dev # Start docs site
npm run widgetbook:dev # Start Flutter widgetbook preview (requires Flutter SDK)
npm run themes:apply-flutter # Regenerate packages/visor_themes/ for all 11 themes
```

### Changesets (every shipping-package change)

Every PR that touches shipping-package source needs a `.changeset/*.md` file. The `Changeset Gate` workflow blocks merge if one is missing or malformed.

**Automatic generation (recommended).** The pre-push git hook runs `scripts/generate-changeset.mjs` before every push. If the diff touches a shipping path (any directory listed in `changeset-paths.json` — `components/`, `blocks/`, `hooks/`, `lib/`, `registry/`, `themes/`, `patterns/`, `assets/`, or the `src/`/`lib/` trees of the published packages) and no operator-authored changeset exists yet, Claude will write `.changeset/.md` and stage it automatically. The same `changeset-paths.json` drives the CI changeset gate, so the local hook and CI stay in sync.

Requirements: `claude` CLI must be installed globally (`npm install -g @anthropic-ai/claude-code`). If it's not available, the hook prints a warning and the push proceeds normally.

**Manual generation.** Run at any time:

```sh
node scripts/generate-changeset.mjs
# or the standard interactive way:
npm run changeset
```

**On-demand via Claude Code.** The `/lo-changeset` skill at `.claude/skills/lo-changeset/SKILL.md` wraps the same script:

```
/lo-changeset
```

**Bypass the hook.** Skip changeset generation for a push:

```sh
git push --no-verify
```

**Auto-generated marker.** Generated changesets include `# generated-by: lo-changeset` in their YAML frontmatter. If you edit the changeset and remove that marker, it becomes operator-authored — the hook will not overwrite it on subsequent pushes. Operator overrides always win.

**Failure handling.** If `claude` fails for any reason, the hook exits 0 and the push proceeds. Run `npm run changeset` manually if you need a minor/major bump and the auto-generation failed.

**Prompt source.** `scripts/changeset-prompt.md` contains the bump-type rules and output format. Edit it to tune the AI's behavior.

### Repository Structure

```
visor/
├── components/ui/ # Component source + .visor.yaml metadata
├── hooks/ # Hook source (registry entries)
├── lib/ # Utility source (registry entries)
├── patterns/ # Composition patterns (.visor-pattern.yaml)
├── registry/ # Registry schema and definitions
└── packages/
├── cli/ # @loworbitstudio/visor CLI + manifest builder
├── tokens/ # @loworbitstudio/visor-core npm package
├── visor-flutter/ # visor_core Flutter package (pub.dev)
├── visor_themes/ # All 11 Visor ThemeData — generated, do not edit
├── widgetbook/ # Flutter widgetbook preview app
└── docs/ # fumadocs documentation site
```

### Flutter widget quality

Every `visor_*` Flutter widget is audited against the [Flutter Widget Quality Contract](./docs/flutter-widget-quality-contract.md) — a tiered checklist (Required / Recommended / Stretch) covering tokens, semantics, touch targets, reduce-motion, RTL, tests, and a11y matchers. Required-tier compliance gates a widget being marked production-ready.

---

## Operator workflows

Day-to-day publishing is automatic for the three public npm packages — `.changeset/*.md` files written on each PR drive the bumps, and `release.yml` opens a "Version Packages" PR that publishes on merge. `@low-orbit-studio/visor-themes-private` still auto-versions on its own merges. The commands below surface only for the rare cases where a human is in the loop: health checks and cross-repo coordinated releases.

### Publishing health and coordinated releases

`/lo-visor-publish` (see [`.claude/skills/lo-visor-publish/SKILL.md`](./.claude/skills/lo-visor-publish/SKILL.md)) has two modes:

- **`status`** — read-only drift report across all 4 publishable artifacts. Non-zero exit on drift, so it can gate other workflows.
```bash
node scripts/visor-publish-status.mjs
```
- **`coordinate `** — single-confirmation cross-repo release for the case where a feature spans Visor + visor-themes-private and both must ship together.
```bash
node scripts/visor-publish-coordinate.mjs 369 2 --dry-run # preview only
node scripts/visor-publish-coordinate.mjs 369 2 # live
```

The skill itself contains no publish logic. Each repo's existing CI (`release.yml` on the Visor side, themes-private's `publish.yml`) remains the source of truth for what publishes. See [`docs/audits/publish-automation.md`](./docs/audits/publish-automation.md) for the full audit.

### Publish-gate audit (PR comment governance)

When the `visor-publish-smoke` workflow detects drift between the source on `main` and the latest published `@loworbitstudio/visor` tarball, the audit step maps each drifted primitive back to the PR that landed it and posts a comment there — so "merged" eventually catches up with "shipped." Uses the built-in `GITHUB_TOKEN`, no extra secrets required. Run it locally with `npm run audit:publish`. Full background in [`CLAUDE.md` § Publish Gate](./CLAUDE.md#publish-gate) and [`docs/wisdom/W029-vi-ticket-publish-governance.md`](./docs/wisdom/W029-vi-ticket-publish-governance.md).

---

## Sustainability

Visor is free and open-source, built and maintained by [Low Orbit Studio](https://loworbit.studio). If it's useful to you, here's how to support it:

- **Use it and share it** — the best support is adoption and word of mouth.
- **Contribute** — bug reports, PRs, and Discussions participation all help.
- **Hire us** — Low Orbit Studio takes on product and design system work. [Get in touch](https://loworbit.studio).

---

## License

See [LICENSE](LICENSE) for details.

---

Built by [Low Orbit Studio](https://loworbit.studio) — Brooklyn, NY.