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 style | Where the CSS lives |
|---|---|
| Colors and radius of the site | theme.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 components | a CSS module next to it, Card.tsx and Card.module.css |
| A few inline rules, no file | theme.customCss (a string or an object; kept for compatibility) |
| A deck published by the site | the deck's own theme.customCss and the modules of its components |
The stylesheet of a site is plain CSS, imported once from the configuration:
recit.config.ts
styles.css
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:
Card.tsx
Card.module.css
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:
styles.css
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
@applyin a deck'stheme.customCss; theme.registryUrl, removed with shadcn/ui: settheme.lightandtheme.darkinstead.
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; composesin a module to share declarations, or to keep a global class;- in a deck, plain declarations in the
theme.customCssstylesheet instead of@apply.
Before, with utilities in a page:
pages/index.mdx
After, with two classes of the site's stylesheet:
pages/index.mdx
styles.css
In a deck, the same @apply becomes its declarations:
styles.css (deck, before)
styles.css (deck, after)
The utilities met most often, and what to write:
| Tailwind | CSS |
|---|---|
flex items-center justify-between | display: flex; align-items: center; justify-content: space-between; |
flex-col / gap-2 / gap-4 | flex-direction: column; / gap: rem(8px); / gap: rem(16px); |
grid grid-cols-3 | display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); |
p-4 / px-3 / mt-8 | padding: rem(16px); / padding-inline: rem(12px); / margin-top: rem(32px); |
w-full / max-w-prose / min-w-0 | width: 100%; / max-width: 65ch; / min-width: 0; |
text-sm / font-semibold | font-size: var(--mantine-font-size-sm); / font-weight: 600; |
text-muted-foreground | color: var(--mantine-color-dimmed); |
bg-background / text-foreground | background: var(--mantine-color-body); / color: var(--mantine-color-text); |
border / rounded-lg | border: rem(1px) solid var(--mantine-color-default-border); / border-radius: var(--mantine-radius-lg); |
shadow-sm | box-shadow: var(--mantine-shadow-sm); |
truncate | overflow: hidden; text-overflow: ellipsis; white-space: nowrap; |
hover:underline | &:hover { text-decoration: underline; } |
dark:bg-zinc-900 | background: 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-only | Mantine'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).