@majistudio/ogcr-design-system
v1.0.0
Published
OGCR design system — React 19 components, design tokens, and Tailwind v4 theme bridge.
Maintainers
Readme
OGCR Design System
@majistudio/ogcr-design-system — the React component library, design tokens, and Tailwind v4 theme bridge for OGCR frontends. This repo is the single source other OGCR projects build on; import from the package rather than reinventing components or re-deriving tokens.
Live preview: demo app · component workbench (Storybook)
Status:
1.0.0, public — published to npm under public access (publishConfig.access: public). The publishable artifact is produced bynpm run build:lib.docs/design-system.mdis the authoritative written spec; when spec and code disagree, the spec wins.
What's in here
src/components/— 42 components on Base UI primitives (plus react-day-picker for the calendar), styled with Tailwind v4 tokens and CVA variants. Overlays (Dialog,AlertDialog,Popover,Select,Combobox,Tooltip,Menu,ContextMenu,Sidesheet), inputs (Input,Textarea,NumberField,Checkbox,Radio,Switch,Slider,Toggle/ToggleGroup,Calendar,DatePicker,Form), structure (Accordion,Collapsible,Tabs,Table,Toolbar,ScrollArea,Separator,Card,Breadcrumb,Pagination,Navigation,SideNavigation), and feedback/display (Toast,Message,Skeleton,ProgressBar,Pill,Kpi,Avatar,Logo,icons).src/index.tsis the public barrel.src/styles/— design tokens as Tailwind v4@themecustom properties (theme.css), a small reset, and theglobal.cssentry. Tokens are reconciled from the OGCR Figma file.docs/design-system.md— the authoritative spec: tokens, component anatomy, and conventions.
Install & use (in an OGCR app)
npm install @majistudio/ogcr-design-system
# peer dependencies the consumer provides:
npm install react react-dom @base-ui/react
# optional — only if you import the Table component:
npm install @tanstack/react-tableimport { Button, Dialog, useToast } from '@majistudio/ogcr-design-system' // barrel (tree-shaken by your bundler)
import { Button } from '@majistudio/ogcr-design-system/Button' // or deep-import a single component
import '@majistudio/ogcr-design-system/styles.css' // tokens + Tailwind utilities + reset (import once)The barrel is ESM with "sideEffects": ["*.css"], so a bundler (Vite/webpack/Rollup/esbuild) tree-shakes the components you don't import. Every component also has a ./<Name> subpath (@majistudio/ogcr-design-system/Button) for explicit deep imports and non-bundler setups — the exports map and subpaths are generated from src/components/* by scripts/generate-lib-meta.mjs during build:lib. The stylesheet does not split — styles.css is one file (~59 KB) regardless of how many components you use; import it once.
Machine-readable indexes ship in the package for tooling and LLM exploration: @majistudio/ogcr-design-system/manifest.json (structured: every component's import path, exported symbols, and types path) and @majistudio/ogcr-design-system/llms.txt (llms.txt format — one line per component with its import). Both are regenerated on every build:lib.
Imports, SSR boundaries & peers — the consumption contract
Tableis deep-import only. It is intentionally not re-exported from the barrel; import it asimport { Table } from '@majistudio/ogcr-design-system/Table'. It is the one component that pulls in@tanstack/react-table, so keeping it off the barrel means consumers who never render a table don't drag that peer into their dependency graph.@tanstack/react-tableis declared an optional peer (peerDependenciesMeta) — install it only if you useTable; the install won't warn otherwise.'use client'boundary. Every component entry — the barrel (@majistudio/ogcr-design-system) and each deep import (@majistudio/ogcr-design-system/Button) — ships with a'use client'directive as its first line. In a React Server Components app (Next.js App Router, etc.) importing from either draws the server/client boundary at the package edge, so the components Just Work from a Server Component. For the smallest client boundary in an RSC/perf-sensitive app, deep-import the specific components you render rather than pulling the whole barrel across the boundary.cn()is exported, and dependency-free. The sameclsx+tailwind-mergeclass-merge helper the components use is available asimport { cn } from '@majistudio/ogcr-design-system'(barrel) or, with no'use client'directive and no React in its graph, asimport { cn } from '@majistudio/ogcr-design-system/cn'— use the/cndeep import when you need to compose class names in a Server Component or other pure context.
Peer dependencies the consumer provides: react/react-dom (^19), @base-ui/react (^1), and @tanstack/react-table (^8, optional — only if you use Table). Icons (@phosphor-icons/react), react-day-picker (used by Calendar/DatePicker), and cva/clsx/tailwind-merge ship as regular dependencies and are externalized from the bundle so a single copy is deduped.
Local development
npm run dev— Vite dev server with HMR (the demo app insrc/App.tsx)npm run storybook— Storybook 10 (component workbench;npm run build-storybookfor a static build)npm run test— jsdom unit suite (Vitest)npm run test:a11y— axe accessibility checks over every story in a headless Chromium (needsnpx playwright install chromium-headless-shell)npm run lint— ESLint over the reponpm run build— type-check + Vite app build (fails on type errors)npm run build:lib— produce the publishabledist/(index.jsESM, bundledindex.d.ts,styles.css)npm run changeset/release— Changesets versioning/publish flow
Publishing (maintainers)
@majistudio/ogcr-design-system publishes to the public npm registry, scoped under the @majistudio org. Scoped packages are private by default, so publishConfig.access: public (already set in package.json) is what makes each release public. Releases are driven by Changesets. The only artifact shipped is dist/ — it is rebuilt and contract-checked automatically on publish by the prepublishOnly hook, so you never publish a stale or hand-built dist/.
One-time setup: npm login as a member of the @majistudio npm org with publish rights. (To publish to a different registry instead — GitHub Packages or a private registry — add a publishConfig.registry pointing at it; nothing else changes.)
Cut a release:
npm run changeset # describe the change, pick the semver bump; commit the generated .changeset/*.md
npm run version # apply pending changesets: bumps package.json + writes the changelog
git commit -am "Version packages" # the changeset config sets commit:false, so commit the bump yourself
npm run release # prepublishOnly builds dist/, then `changeset publish` publishes + tags
git push --follow-tags # push the version commit + the release tag changeset createdUnder the hood, prepublishOnly runs npm run build:lib, which emits dist/ (ESM index.js, bundled index.d.ts, styles.css, manifest.json, llms.txt) and runs check:tokens + check:dist. check:dist asserts the publish contract — every entry starts with 'use client', cn.js stays pure (server-importable), Table is deep-import-only, and npm pack --dry-run actually ships dist/index.js. A failed contract fails the publish. The tarball is governed by the files: ["dist"] allowlist (npm also includes package.json, README.md, and LICENSE automatically).
Preview the exact tarball without publishing:
npm run build:lib && npm pack --dry-runStack notes
- React 19 + TypeScript + Vite, Tailwind v4 tokens, Base UI behavior primitives, CVA +
cn()for variants. - React Compiler is on (
@rolldown/plugin-babel+reactCompilerPreset()). Components auto-memoize at build time — skip manualuseMemo/useCallback/React.memounless the compiler can't (e.g.Table). - Theming is Tailwind v4
@theme inlineover a runtime--ds-*seam. Extend the token layer insrc/styles/theme.css; consume the semantic tokens (bg-surface-*,text-text-*,shadow-focus-*), not raw hex. Every brand color resolves through a--ds-*custom property insrc/styles/palette.css, so the utilities and focus shadows are runtime-themeable — override a--ds-*on any scoping element and everything derived from it retints with no rebuild (see the Foundations → Theming story, andnpm run check:tokenswhich fails the build if a color utility re-bakes a literal). There is no dark mode shipped yet — the color layer is in place, so a dark theme is a.darkpalette override rather than a rearchitecture. SeeCLAUDE.mdfor the@theme inlinemechanics. - TypeScript project references:
tsconfig.jsondelegates totsconfig.app.jsonandtsconfig.node.json;tsc -bmust pass for both. - ESLint flat config in
eslint.config.js;dist/is globally ignored.
See CLAUDE.md for the non-obvious, cross-file details (the two Vitest setups, @theme inline themability, Base UI prop gotchas, the a11y gate, and library-build externalization).
