Skip to content

Installation

How to add Cynosure to a Next.js, Vite, Remix, or CRA project.

Terminal window
npx @arshad-shah/cynosure-cli init

Detects your framework (Next.js App Router, Next.js Pages, Vite, CRA, Remix), prints the exact install command for your package manager, writes the single CSS import, and wires up CynosureProvider (for the Next.js App Router it also scaffolds a providers.tsx). Add --dry-run to preview.

  • Node 22 or newer for local development (Cynosure is authored against ESM and uses native TypeScript loading).
  • React 18 or 19 (peer dependency). Works with Next.js 13 / 14 / 15, Vite, CRA, and Remix.
Terminal window
npm install @arshad-shah/cynosure-react @arshad-shah/cynosure-tokens
import '@arshad-shah/cynosure-react/all.css';

This single file bundles tokens (light + dark) and every component’s CSS. For most apps it’s the right choice.

The default token font stacks resolve to Geist Variable (sans) and JetBrains Mono Variable (mono), falling through to system fonts when those families aren’t loaded. Add one more import to bundle them:

import '@arshad-shah/cynosure-react/fonts.css';

Kept separate from all.css so you don’t pay the ~400 KB woff2 cost if you have your own font pipeline (next/font, self-hosted, CDN). With next/font, skip this import and override --cynosure-font-family-sans / -mono with your font’s CSS variable.

If you want to load tokens and component styles separately — useful when you want to inline critical CSS, ship dark overrides conditionally, or control cascade order yourself:

import '@arshad-shah/cynosure-tokens/css'; // design tokens, light theme
import '@arshad-shah/cynosure-tokens/css/dark'; // dark overrides (optional)
import '@arshad-shah/cynosure-react/styles.css'; // component styles barrel

If you import per-component, your bundler wires up the CSS automatically:

import { Button } from '@arshad-shah/cynosure-react/button';

Every per-component JS entry prepends its CSS imports, so subpath imports never need a manual stylesheet include. Each component imports core.css (the universal scaffolding — the layout-prop @property declarations and the body reset) followed by only the shared chunks under shared/ that it participates in, then its own rules. Shared rules are split by which components use them, so a single-component import pulls just that component’s CSS (~1.5–3 kB brotli) rather than the whole shared baseline — and importing many components still deduplicates each chunk to one copy.

If you’re code-splitting and want to preload the universal scaffolding yourself, it’s reachable directly:

import '@arshad-shah/cynosure-react/core.css';

CynosureProvider composes ThemeProvider, DirectionProvider, LocaleProvider, and the global TooltipProvider in a single wrapper:

import { CynosureProvider } from '@arshad-shah/cynosure-react';
<CynosureProvider theme={{ defaultTheme: 'system' }}>
<App />
</CynosureProvider>

Everything is optional — pass only what you want to override. The individual providers are still exported if you prefer to compose them yourself.

Create a providers.tsx client boundary to wrap the server root layout:

app/providers.tsx
'use client';
import { CynosureProvider } from '@arshad-shah/cynosure-react';
export function Providers({ children }: { children: React.ReactNode }) {
return <CynosureProvider>{children}</CynosureProvider>;
}
app/layout.tsx
import '@arshad-shah/cynosure-react/all.css';
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
pages/_app.tsx
import '@arshad-shah/cynosure-react/all.css';
import type { AppProps } from 'next/app';
import { CynosureProvider } from '@arshad-shah/cynosure-react';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<CynosureProvider>
<Component {...pageProps} />
</CynosureProvider>
);
}
src/main.tsx
import '@arshad-shah/cynosure-tokens/css';
import '@arshad-shah/cynosure-tokens/css/dark';
import '@arshad-shah/cynosure-react/styles.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { CynosureProvider } from '@arshad-shah/cynosure-react';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<CynosureProvider>
<App />
</CynosureProvider>
</React.StrictMode>,
);
app/root.tsx
import '@arshad-shah/cynosure-react/all.css';
import { CynosureProvider } from '@arshad-shah/cynosure-react';
import { Outlet } from '@remix-run/react';
export default function App() {
return (
<CynosureProvider>
<Outlet />
</CynosureProvider>
);
}
src/index.tsx
import '@arshad-shah/cynosure-react/all.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { CynosureProvider } from '@arshad-shah/cynosure-react';
import App from './App';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<CynosureProvider>
<App />
</CynosureProvider>
</React.StrictMode>,
);
import '@arshad-shah/cynosure-themes/terminal';
import '@arshad-shah/cynosure-themes/high-contrast';

Cynosure is authored in TypeScript and publishes types with full JSDoc. If you use skipLibCheck: false, make sure your lib includes DOM and DOM.Iterable.

PeerRangeRequired
react>=18yes
react-dom>=18yes
react-hook-form^7only if you import @arshad-shah/cynosure-react/rhf

See the Quickstart for a minimal end-to-end example, or the RSC guide for Next.js App Router integration.