Advanced

Mantine and CSS modules

A Récit site is drawn by Mantine and plain CSS: every component of the framework has a CSS module next to it, and the site adds its own stylesheet on top. There is no utility-class framework: a class such as flex gap-2 in a page styles nothing.

Where a site's styles go

What you styleWhere the CSS lives
Colors and radius of the sitetheme.light and theme.dark in recit.config.ts
Global rules (pages, selectors)a stylesheet imported by recit.config.ts, for example styles.css
One of your componentsa CSS module next to it, Card.tsx and Card.module.css
A few inline rules, no filetheme.customCss (a string or an object; kept for compatibility)
A deck published by the sitethe deck's own theme.customCss and the modules of its components

The stylesheet of a site is plain CSS, imported once from the configuration:

TypeScriptrecit.config.ts
import { defineConfig } from "recit/config"; import "./styles.css"; export default defineConfig({ theme: { light: { primary: "oklch(0.55 0.2 260)" }, dark: { primary: "oklch(0.75 0.15 260)" }, }, });
CSSstyles.css
.lorem-hero { padding-block: 3rem; color: var(--mantine-color-text); background: var(--mantine-color-body); }

Mantine, the tokens, the reset and most of the framework's modules sit in cascade layers (@layer mantine, theme, base, components, utilities), declared before any other sheet. A rule you write outside a layer wins over those, whatever their specificity: reach for a layer-free rule before !important.

Read colors, spacing and radius from Mantine's variables (--mantine-color-text, --mantine-color-dimmed, --mantine-primary-color-filled, --mantine-spacing-md, --mantine-radius-md…) so that your rules follow the theme of the site.

CSS modules

A component of the site (in mdx.components, a slot or a custom page) imports a module next to it. Vite compiles it with Mantine's PostCSS preset, so rem(), light-dark() and the breakpoint mixins work there:

ReactCard.tsx
import type { ReactNode } from "react"; import classes from "./Card.module.css"; export const Card = ({ children }: { children: ReactNode }) => ( <div className={classes.root}>{children}</div> );
CSSCard.module.css
.root { display: flex; flex-direction: column; gap: rem(8px); padding: rem(16px); border: rem(1px) solid var(--mantine-color-default-border); border-radius: var(--mantine-radius-md); @mixin larger-than $mantine-breakpoint-sm { flex-direction: row; } }

Classes combine inside the module with composes, never by concatenating strings in the JSX. A class that must stay global, because a stylesheet or a deck targets it, is kept with composes: lorem-card from global.

The components of a deck (components: in the deck's recit.config.ts) are bundled by the CLI, without PostCSS: their modules are plain CSS (lengths in rem, the @recit/theme tokens such as hsl(var(--primary)), plain @media queries), and composes works the same way.

Color mode

The mode is the data-mantine-color-scheme attribute on <html>, light or dark. Write both values in one declaration with light-dark(), or select on the attribute:

CSSstyles.css
.lorem-panel { background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7)); } :root[data-mantine-color-scheme="dark"] .lorem-logo { filter: invert(1); }

A .dark class on <html> and Tailwind's dark: prefix no longer exist. Decks embedded in a site follow its mode; see Dark mode for a deck on its own.

Migrating a site from Tailwind utilities

Récit sites and decks used to compile Tailwind CSS: the utility classes written in a site's MDX pages, mdx.components, slots and components, and @apply in a deck's stylesheet. Tailwind is gone, and these classes now produce nothing. The page still renders, unstyled where the utilities were.

What stops working:

  • utility classes in MDX (<div className="grid grid-cols-2 gap-4">);
  • utility classes in the components passed to mdx.components, to slots or to custom pages;
  • utility classes in the components of a deck, and @apply in a deck's theme.customCss;
  • theme.registryUrl, removed with shadcn/ui: set theme.light and theme.dark instead.

What to write instead:

  • a class of your own in the site's stylesheet, used from MDX (className="lorem-grid");
  • a CSS module next to each component, imported as classes;
  • composes in a module to share declarations, or to keep a global class;
  • in a deck, plain declarations in the theme.customCss stylesheet instead of @apply.

Before, with utilities in a page:

MDXpages/index.mdx
<div className="grid grid-cols-1 gap-4 md:grid-cols-3"> <div className="rounded-lg border p-4 text-muted-foreground">Lorem ipsum dolor sit amet.</div> <div className="rounded-lg border p-4 text-muted-foreground">Consectetur adipiscing elit.</div> </div>

After, with two classes of the site's stylesheet:

MDXpages/index.mdx
<div className="lorem-grid"> <div className="lorem-cell">Lorem ipsum dolor sit amet.</div> <div className="lorem-cell">Consectetur adipiscing elit.</div> </div>
CSSstyles.css
.lorem-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; } @media (min-width: 48em) { .lorem-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } } .lorem-cell { padding: 1rem; border: 1px solid var(--mantine-color-default-border); border-radius: var(--mantine-radius-lg); color: var(--mantine-color-dimmed); }

In a deck, the same @apply becomes its declarations:

CSSstyles.css (deck, before)
.metric-value { @apply text-5xl font-extrabold text-primary; }
CSSstyles.css (deck, after)
.metric-value { font-size: 3rem; font-weight: 800; color: hsl(var(--primary)); }

The utilities met most often, and what to write:

TailwindCSS
flex items-center justify-betweendisplay: flex; align-items: center; justify-content: space-between;
flex-col / gap-2 / gap-4flex-direction: column; / gap: rem(8px); / gap: rem(16px);
grid grid-cols-3display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
p-4 / px-3 / mt-8padding: rem(16px); / padding-inline: rem(12px); / margin-top: rem(32px);
w-full / max-w-prose / min-w-0width: 100%; / max-width: 65ch; / min-width: 0;
text-sm / font-semiboldfont-size: var(--mantine-font-size-sm); / font-weight: 600;
text-muted-foregroundcolor: var(--mantine-color-dimmed);
bg-background / text-foregroundbackground: var(--mantine-color-body); / color: var(--mantine-color-text);
border / rounded-lgborder: rem(1px) solid var(--mantine-color-default-border); / border-radius: var(--mantine-radius-lg);
shadow-smbox-shadow: var(--mantine-shadow-sm);
truncateoverflow: hidden; text-overflow: ellipsis; white-space: nowrap;
hover:underline&:hover { text-decoration: underline; }
dark:bg-zinc-900background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7));
md:grid-cols-3@mixin larger-than $mantine-breakpoint-sm { … } (48em, Tailwind's md)
sr-onlyMantine's VisuallyHidden component

rem() and the mixins exist in the modules and stylesheets of a site (Mantine's PostCSS preset); in a deck, write 1rem and plain @media (min-width: 48em).