From 7fbae66d68a717aed5545103347a772cc0599ee5 Mon Sep 17 00:00:00 2001
From: sadman <68962712+sadmann7@users.noreply.github.com>
Date: Sun, 20 Sep 2026 20:52:36 +0600
Subject: [PATCH] chore(registry): cleanup styles
---
components.json | 2 +-
next.config.ts | 19 ++--
package.json | 4 +-
public/r/styles/index.json | 32 +++---
registry/bases.ts | 32 ++++++
registry/styles.tsx | 194 +++++++++++++++++++++++++++++++++++++
scripts/build-registry.ts | 47 +++++----
scripts/clean-registry.ts | 14 ++-
src/registry/styles.ts | 33 -------
tsconfig.scripts.json | 26 +++++
10 files changed, 318 insertions(+), 85 deletions(-)
create mode 100644 registry/bases.ts
create mode 100644 registry/styles.tsx
delete mode 100644 src/registry/styles.ts
create mode 100644 tsconfig.scripts.json
diff --git a/components.json b/components.json
index 5233ece6..f71e5f71 100644
--- a/components.json
+++ b/components.json
@@ -1,6 +1,6 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
- "style": "radix-nova",
+ "style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
diff --git a/next.config.ts b/next.config.ts
index bef86b76..f7bd20e9 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -6,22 +6,27 @@ import type { NextConfig } from "next";
*/
// Import environment validation synchronously
import "./src/env.js";
-import { DEFAULT_STYLE_ID, getStyleIds } from "./src/registry/styles";
const nextConfig: NextConfig = {
cacheComponents: true,
+ async redirects() {
+ return [
+ {
+ source: "/docs/components/:name((?!radix|base)[^/]+)",
+ destination: "/docs/components/base/:name",
+ permanent: false,
+ },
+ ];
+ },
async rewrites() {
return [
- // Published style ids resolve to their static directory.
{
- source: `/r/:style(${getStyleIds().join("|")})/:name.json`,
+ source: "/r/:style/:name.json",
destination: "/r/styles/:style/:name.json",
},
- // Bases we don't author (`aria-*`) and legacy v3 ids fall back to the
- // default rather than 404ing on an otherwise valid install.
{
- source: "/r/:style/:name.json",
- destination: `/r/styles/${DEFAULT_STYLE_ID}/:name.json`,
+ source: "/r/:name.json",
+ destination: "/r/styles/base-nova/:name.json",
},
];
},
diff --git a/package.json b/package.json
index 222a2a2d..1e4abf76 100644
--- a/package.json
+++ b/package.json
@@ -20,8 +20,8 @@
"typecheck": "tsc --noEmit",
"check": "oxlint . && oxfmt --check && tsc --noEmit",
"shadcn": "pnpm dlx shadcn@latest",
- "build:registry": "tsx scripts/build-registry.ts",
- "clean:registry": "tsx scripts/clean-registry.ts",
+ "build:registry": "tsx --tsconfig ./tsconfig.scripts.json scripts/build-registry.ts",
+ "clean:registry": "tsx --tsconfig ./tsconfig.scripts.json scripts/clean-registry.ts",
"db:start": "docker compose up -d",
"db:stop": "docker compose down",
"db:generate": "dotenv drizzle-kit generate",
diff --git a/public/r/styles/index.json b/public/r/styles/index.json
index 40c7daf5..9e12e942 100644
--- a/public/r/styles/index.json
+++ b/public/r/styles/index.json
@@ -1,50 +1,50 @@
[
{
- "name": "radix-nova"
+ "name": "base-vega"
},
{
- "name": "radix-vega"
+ "name": "base-nova"
},
{
- "name": "radix-maia"
+ "name": "base-maia"
},
{
- "name": "radix-lyra"
+ "name": "base-lyra"
},
{
- "name": "radix-mira"
+ "name": "base-mira"
},
{
- "name": "radix-luma"
+ "name": "base-luma"
},
{
- "name": "radix-sera"
+ "name": "base-sera"
},
{
- "name": "radix-rhea"
+ "name": "base-rhea"
},
{
- "name": "base-nova"
+ "name": "radix-vega"
},
{
- "name": "base-vega"
+ "name": "radix-nova"
},
{
- "name": "base-maia"
+ "name": "radix-maia"
},
{
- "name": "base-lyra"
+ "name": "radix-lyra"
},
{
- "name": "base-mira"
+ "name": "radix-mira"
},
{
- "name": "base-luma"
+ "name": "radix-luma"
},
{
- "name": "base-sera"
+ "name": "radix-sera"
},
{
- "name": "base-rhea"
+ "name": "radix-rhea"
}
]
diff --git a/registry/bases.ts b/registry/bases.ts
new file mode 100644
index 00000000..604514a8
--- /dev/null
+++ b/registry/bases.ts
@@ -0,0 +1,32 @@
+/**
+ * @see https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/bases.ts
+ */
+
+import type { RegistryItem } from "shadcn/schema";
+
+export const BASES: RegistryItem[] = [
+ {
+ name: "base",
+ type: "registry:style",
+ title: "Base UI",
+ description:
+ "Components for building accessible web apps and design systems.",
+ dependencies: ["@base-ui/react"],
+ meta: {
+ logo: "",
+ },
+ },
+ {
+ name: "radix",
+ type: "registry:style",
+ title: "Radix UI",
+ description:
+ "Optimized for fast development, easy maintenance, and accessibility.",
+ dependencies: ["radix-ui"],
+ meta: {
+ logo: "",
+ },
+ },
+] as const;
+
+export type Base = (typeof BASES)[number];
diff --git a/registry/styles.tsx b/registry/styles.tsx
new file mode 100644
index 00000000..ad99855a
--- /dev/null
+++ b/registry/styles.tsx
@@ -0,0 +1,194 @@
+/**
+ * @see https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/styles.tsx
+ */
+
+export const STYLES = [
+ {
+ name: "vega",
+ title: "Vega",
+ description: "Clean, neutral, and familiar",
+ icon: (
+
+ ),
+ },
+ {
+ name: "nova",
+ title: "Nova",
+ description: "Reduced padding and margins",
+ icon: (
+
+ ),
+ },
+ {
+ name: "maia",
+ title: "Maia",
+ description: "Rounded, with generous spacing.",
+ icon: (
+
+ ),
+ },
+ {
+ name: "lyra",
+ title: "Lyra",
+ description: "Boxy and sharp. For mono fonts.",
+ icon: (
+
+ ),
+ },
+ {
+ name: "mira",
+ title: "Mira",
+ description: "Made for compact interfaces.",
+ icon: (
+
+ ),
+ },
+ {
+ name: "luma",
+ title: "Luma",
+ description: "Fluid, luminous, and soft.",
+ icon: (
+
+ ),
+ },
+ {
+ name: "sera",
+ title: "Sera",
+ description: "Editorial and typographic.",
+ icon: (
+
+ ),
+ },
+ {
+ name: "rhea",
+ title: "Rhea",
+ description: "Like Luma but compact.",
+ icon: (
+
+ ),
+ },
+] as const;
+
+export type Style = (typeof STYLES)[number];
diff --git a/scripts/build-registry.ts b/scripts/build-registry.ts
index 6a805cd5..77e8ac9f 100644
--- a/scripts/build-registry.ts
+++ b/scripts/build-registry.ts
@@ -12,6 +12,8 @@
* to its base-tree counterpart when one exists. Files outside the bases (hooks,
* lib) and primitives with no base variant stay shared, so base-agnostic code
* lives in exactly one place.
+ *
+ * @see https://github.com/shadcn-ui/ui/blob/main/apps/v4/scripts/build-registry.mts
*/
import { execFileSync } from "node:child_process";
import {
@@ -26,16 +28,21 @@ import {
import { tmpdir } from "node:os";
import path from "node:path";
-import {
- type Base,
- BASES,
- DEFAULT_STYLE_ID,
- getStyleIds,
- STYLES,
-} from "../src/registry/styles";
+import { BASES } from "../registry/bases";
+import { STYLES } from "../registry/styles";
import { OUT_ROOT, STYLES_ROOT, wipeRegistryOutput } from "./clean-registry";
-const DEFAULT_BASE = DEFAULT_STYLE_ID.split("-")[0] as Base;
+const DEFAULT_STYLE = "nova";
+const DEFAULT_BASE = "base";
+
+const STYLE_COMBINATIONS = BASES.flatMap((base) =>
+ STYLES.map((style) => ({
+ base,
+ style,
+ name: `${base.name}-${style.name}`,
+ title: `${base.title} ${style.title}`,
+ })),
+);
const ROOT = process.cwd();
const RADIX_TREE = "src/registry/bases/radix/";
@@ -61,8 +68,8 @@ interface Registry {
* Repoints a file at the base tree when an override exists there.
* `target` is deliberately untouched so consumers get the same paths either way.
*/
-function resolveFile(file: RegistryFile, base: Base): RegistryFile {
- if (base === "radix") return file;
+function resolveFile(file: RegistryFile, baseName: string): RegistryFile {
+ if (baseName === "radix") return file;
const override = file.path.replace(RADIX_TREE, BASE_TREE);
@@ -103,13 +110,13 @@ function withImportedDependencies(
return next.length > 0 ? next : dependencies;
}
-function buildBase(base: Base, outDir: string) {
+function buildBase(baseName: string, outDir: string) {
const registry = JSON.parse(
readFileSync(path.join(ROOT, "registry.json"), "utf8"),
) as Registry;
const items = registry.items.map((item) => {
- const files = item.files?.map((file) => resolveFile(file, base));
+ const files = item.files?.map((file) => resolveFile(file, baseName));
return {
...item,
@@ -143,45 +150,43 @@ function main() {
try {
for (const base of BASES) {
- const baseDir = path.join(scratch, base);
+ const baseDir = path.join(scratch, base.name);
mkdirSync(baseDir, { recursive: true });
- const overrideCount = buildBase(base, baseDir);
+ const overrideCount = buildBase(base.name, baseDir);
const built = path.join(baseDir, "r");
for (const style of STYLES) {
- cpSync(built, path.join(STYLES_ROOT, `${base}-${style}`), {
+ cpSync(built, path.join(STYLES_ROOT, `${base.name}-${style.name}`), {
recursive: true,
});
}
// Flat `/r/{name}.json` keeps serving the default base so existing
// DiceUI redirects and style-less installs keep working.
- if (base === DEFAULT_BASE) {
+ if (base.name === DEFAULT_BASE) {
cpSync(built, OUT_ROOT, { recursive: true });
}
console.log(
- `${base}: ${overrideCount} base-tree file(s), ${STYLES.length} style ids`,
+ `${base.name}: ${overrideCount} base-tree file(s), ${STYLES.length} style ids`,
);
}
} finally {
rmSync(scratch, { recursive: true, force: true });
}
- const ids = getStyleIds();
-
writeFileSync(
path.join(STYLES_ROOT, "index.json"),
`${JSON.stringify(
- ids.map((name) => ({ name })),
+ STYLE_COMBINATIONS.map(({ name }) => ({ name })),
null,
2,
)}\n`,
);
console.log(
- `\nPublished ${ids.length} style ids (default: ${DEFAULT_BASE}).`,
+ `\nPublished ${STYLE_COMBINATIONS.length} style ids (default: ${DEFAULT_BASE}-${DEFAULT_STYLE}).`,
);
}
diff --git a/scripts/clean-registry.ts b/scripts/clean-registry.ts
index ad746a8b..f45dfe86 100644
--- a/scripts/clean-registry.ts
+++ b/scripts/clean-registry.ts
@@ -15,7 +15,8 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
-import { getStyleIds } from "../src/registry/styles";
+import { BASES } from "../registry/bases";
+import { STYLES } from "../registry/styles";
const ROOT = process.cwd();
const OUT_ROOT = path.join(ROOT, "public/r");
@@ -44,10 +45,13 @@ function getExpectedFiles() {
files.add(path.join(OUT_ROOT, `${name}.json`));
}
- for (const styleId of getStyleIds()) {
- files.add(path.join(STYLES_ROOT, styleId, "registry.json"));
- for (const name of items) {
- files.add(path.join(STYLES_ROOT, styleId, `${name}.json`));
+ for (const base of BASES) {
+ for (const style of STYLES) {
+ const styleId = `${base.name}-${style.name}`;
+ files.add(path.join(STYLES_ROOT, styleId, "registry.json"));
+ for (const name of items) {
+ files.add(path.join(STYLES_ROOT, styleId, `${name}.json`));
+ }
}
}
diff --git a/src/registry/styles.ts b/src/registry/styles.ts
deleted file mode 100644
index 5064a3b3..00000000
--- a/src/registry/styles.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * The style ids we publish, shared by the registry build and the Next rewrites so
- * the two can't drift.
- *
- * shadcn composes a style id as `{base}-{style}`. Upstream ships three bases
- * (`radix`, `base`, `aria`); we author two of them, so `aria-*` and any legacy
- * v3 id (`new-york`, `default`) fall back to the default base at the routing
- * layer rather than 404ing.
- */
-const BASES = ["radix", "base"] as const;
-
-const STYLES = [
- "nova",
- "vega",
- "maia",
- "lyra",
- "mira",
- "luma",
- "sera",
- "rhea",
-] as const;
-
-type Base = (typeof BASES)[number];
-type Style = (typeof STYLES)[number];
-
-/** Must match `style` in components.json; also what flat `/r/{name}.json` serves. */
-const DEFAULT_STYLE_ID = "radix-nova";
-
-function getStyleIds() {
- return BASES.flatMap((base) => STYLES.map((style) => `${base}-${style}`));
-}
-
-export { BASES, DEFAULT_STYLE_ID, getStyleIds, STYLES, type Base, type Style };
diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json
new file mode 100644
index 00000000..f765b39e
--- /dev/null
+++ b/tsconfig.scripts.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM"],
+ "jsx": "react-jsx",
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "moduleDetection": "force",
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "noEmit": true,
+ "types": ["node"]
+ },
+ "include": [
+ "scripts/**/*.ts",
+ "scripts/**/*.mts",
+ "registry/**/*.ts",
+ "registry/**/*.tsx"
+ ],
+ "exclude": ["node_modules"]
+}