Combining styling setups
The three styling options compose. You don’t have to choose one for the whole app — every Cratis wrapper still exposes the same building blocks, so you can combine them per-component or per-region.
Themed app with one unstyled island
Section titled “Themed app with one unstyled island”Keep the PrimeReact theme as your global baseline and opt one specific component out with the per-instance unstyled prop:
import 'primereact/resources/themes/lara-dark-blue/theme.css';import '@cratis/components/styles';import { CratisComponentsProvider } from '@cratis/components';import { Dialog } from '@cratis/components/Dialogs';
const brandDialogPt = { root: { className: 'rounded-3xl bg-violet-900 text-violet-50' }, header: { className: 'px-6 py-4 border-b border-violet-700 font-semibold' }, content: { className: 'p-6' },};
export const App = () => ( <CratisComponentsProvider> <YourApp />
{/* This one Dialog opts out of the theme and uses its own brand visuals. */} <Dialog title="Brand callout" unstyled pt={brandDialogPt}> … </Dialog> </CratisComponentsProvider>);Unstyled app with one themed island
Section titled “Unstyled app with one themed island”Run the app fully unstyled and restore PrimeReact’s defaults inside a single subtree by nesting a second CratisComponentsProvider:
import 'primereact/resources/themes/lara-dark-blue/theme.css';import '@cratis/components/styles';import { CratisComponentsProvider } from '@cratis/components';import { globalPt } from './pt-preset';
export const App = () => ( <CratisComponentsProvider value={{ unstyled: true, pt: globalPt }}> <YourApp />
{/* Inside this subtree, components use PrimeReact's theme defaults. */} <CratisComponentsProvider value={{ unstyled: false }}> <PrimeReactThemedSubtree /> </CratisComponentsProvider> </CratisComponentsProvider>);App-wide dark mode
Section titled “App-wide dark mode”Put each palette behind a class on the root element and toggle the class with your theme switcher. PrimeReact widgets and Cratis surfaces both follow because the --cratis-* tokens cascade from PrimeReact variables by default:
:root.theme-light { --surface-card: #ffffff; --surface-border: #e2e8f0; --text-color: #0f172a; --primary-color: #2563eb;}
:root.theme-dark { --surface-card: #1e293b; --surface-border: #334155; --text-color: #f8fafc; --primary-color: #38bdf8;}const ThemeToggle = () => { const toggle = () => { const root = document.documentElement; root.classList.toggle('theme-dark'); root.classList.toggle('theme-light'); }; return <button onClick={toggle}>Toggle theme</button>;};Combine with prefers-color-scheme for the initial mode:
@media (prefers-color-scheme: dark) { :root:not(.theme-light):not(.theme-dark) { --surface-card: #1e293b; --text-color: #f8fafc; }}Per-region brand zones
Section titled “Per-region brand zones”Token overrides cascade, so any ancestor scope works for tinting Cratis-scoped surfaces in a region:
.brand-zone { --cratis-surface-border: #c4b5fd; --cratis-text-color-secondary: #a78bfa; --cratis-primary-color: #7c3aed;}<div className="brand-zone"> <ObjectNavigationalBar navigationPath={path} onNavigate={…} /> <ObjectContentEditor object={data} schema={schema} /></div>If you want PrimeReact widgets in the region to follow too, override the PrimeReact variables in the same scope:
.brand-zone { --surface-card: #1f1147; --text-color: #ede9fe; --primary-color: #a78bfa; /* …and the --cratis-* siblings above */}Per-component visual override inside unstyled mode
Section titled “Per-component visual override inside unstyled mode”When you’re using fully unstyled mode globally, single components can still pull in classes from a separate stylesheet via the className prop or per-instance pt:
import './custom-table.css';
<CratisComponentsProvider value={{ unstyled: true, pt: globalPt }}> {/* All other DataTables use globalPt; this one uses a bespoke look. */} <DataTableForQuery query={AllAuthors} emptyMessage="No authors" className="custom-table" pt={{ table: { className: 'custom-table__inner' } }} > … </DataTableForQuery></CratisComponentsProvider>What to keep in mind
Section titled “What to keep in mind”- Provider value updates re-render: changing
valueonCratisComponentsProviderrebuilds the merged config. Use a stable reference (e.g.useMemoor a module-level constant) to avoid spurious re-renders. ptmerging is deep: PrimeReact merges globalptwith per-instanceptby default. SetptOptions={{ mergeSections: false }}on the wrapper if you need a hard replace.- Cratis tokens are scoped: overriding a
--cratis-*token only changes Cratis surfaces. To repaint PrimeReact widgets too, override the PrimeReact variable. See Cratis token reference.