Skip to content
cccskills
FreePublished on GitHub

web-ui-mantine

Mantine v7 component library — theming, styling, hooks, forms, notifications

The English summary is being prepared. Showing the original description.

See how to install

Files included (7)

  • SKILL.md14.1 KB
  • examples/components.md5.5 KB
  • examples/core.md8.1 KB
  • examples/forms.md8.4 KB
  • examples/theming.md6.1 KB
  • metadata.yaml503 B
  • reference.md8.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Mantine v7 Component Patterns

Quick Guide: Mantine is a batteries-included React component library whose v7 rewrite replaced Emotion with CSS Modules, so there is no CSS-in-JS runtime. Wrap the app in MantineProvider with a createTheme object, install postcss-preset-mantine for the mixins and light-dark(), and style through one of four routes: style props for one-offs, the Styles API (classNames / styles) for inner elements, CSS Modules for custom components, and theme.components for app-wide defaults. @mantine/form, @mantine/hooks, @mantine/notifications and @mantine/dates are separate packages, each with its own stylesheet.

Detailed Resources:

  • examples/core.md — provider and PostCSS setup, SSR, CSS Modules, Styles API, style props, AppShell, loading states
  • examples/components.md — Modal, Drawer, Menu, Tabs, Accordion, notifications, dates
  • examples/theming.md — custom colours, virtualColor, component defaults, auto-contrast, theme merging
  • examples/forms.md — useForm validation, cross-field rules, nested fields, dynamic lists, schema resolvers
  • reference.md — component selection tree, style-prop and mixin tables, Styles API selectors, package list, anti-patterns

<critical_requirements>

Before writing Mantine code

Wrap the app in MantineProvider and import @mantine/core/styles.css at the root. Every component reads its CSS variables from the provider, and without the stylesheet they render as unstyled markup rather than failing loudly.

Install and configure postcss-preset-mantine. It compiles @mixin smaller-than, @mixin hover, light-dark() and rem(). Without it those lines pass through to the browser as invalid CSS and are dropped.

Write custom component styles as CSS Modules. createStyles was removed in v7, and the theme's values are available to plain CSS as --mantine-* custom properties.

Give a custom colour all ten shades. The palette is indexed 0 (lightest) to 9 (darkest) and the type is a 10-tuple, so a short array is a compile error rather than a runtime surprise.

</critical_requirements>


Auto-detection: Mantine, @mantine/core, @mantine/hooks, @mantine/form, @mantine/notifications, @mantine/dates, MantineProvider, createTheme, virtualColor, useMantineColorScheme, useComputedColorScheme, ColorSchemeScript, mantineHtmlProps, useDisclosure, useDebouncedValue, postcss-preset-mantine, light-dark(), getInputProps, form.key(), classNames prop, styles prop, Component.extend()

Applies to:

  • Building on Mantine's 100+ components, and customising them through the theme rather than by forking them
  • Choosing between style props, the Styles API, CSS Modules and theme defaults for a given override
  • Form state with @mantine/form — validation, nested paths, dynamic lists
  • Colour scheme handling, including the SSR flash and the auto resolution trap
  • The utility hooks in @mantine/hooks

Handled elsewhere:

  • CSS methodology and design-token architecture beyond Mantine's own theme — the theme publishes --mantine-* custom properties, and how the rest of your stylesheet is organised is settled elsewhere
  • Server state — components take data as props, and where it was fetched is not their concern
  • Global client state beyond a component's own — useDisclosure is a local boolean, not a store
  • Schema definition — useForm accepts a resolver, and the schema library that produces it is settled by whatever owns validation

<philosophy>

Mantine is pre-built rather than copy-and-own: you configure components through a theme, not by holding their source. Two consequences shape everything below.

The theme is the customisation surface. Colours, radii, fonts, breakpoints and per-component default props all live in one createTheme object, and every component reads them as CSS variables. An override written at the call site is a decision that will need repeating.

Every component exposes its internals by name. The Styles API gives each inner element a selector — input, label, error, dropdown — reachable through classNames and styles without wrapping or forking the component. That is why v7 could drop CSS-in-JS: nothing needs to run at render time to target an inner node.

</philosophy>

<decision_framework>

Which styling route

One-off spacing or colour            → style props: p="md" bg="blue.1"
Every instance of a component        → theme.components with Component.extend()
An inner element, with pseudo-classes → classNames + CSS Modules
An inner element, quick and inline   → styles prop (no pseudo-classes)
A component of your own              → CSS Modules using var(--mantine-*)
Responsive, in JSX                   → style props with object syntax: w={{ base: "100%", sm: 400 }}
Responsive, in CSS                   → @mixin smaller-than / larger-than
Light/dark conditional               → light-dark() or @mixin light / @mixin dark

Full component-selection tree and the Styles API selector table: reference.md.

</decision_framework>


<patterns>

Core patterns

Pattern 1: Provider and theme

import "@mantine/core/styles.css";
import { createTheme, MantineProvider } from "@mantine/core";

const theme = createTheme({ primaryColor: "blue", defaultRadius: "md" });

export function App() {
  return <MantineProvider theme={theme}>{/* app */}</MantineProvider>;
}

Create the theme at module scope — inline in the JSX it is a new object every render, and every consumer re-renders with it. Server rendering additionally needs ColorSchemeScript in <head> and mantineHtmlProps on <html>, or the first paint flashes the wrong scheme.

Full code, including postcss.config.cjs: examples/core.md


Pattern 2: Components share one prop vocabulary

<Button variant="light" color="red" size="md" radius="sm" loading={isSaving} leftSection={<Icon />}>
  Delete
</Button>
<TextInput label="Email" placeholder="you@example.com" withAsterisk error={errors.email} />
<Select label="Role" data={["Admin", "Editor", "Viewer"]} searchable clearable />
<Group gap="md">{/* row */}</Group>
<Stack gap="sm">{/* column */}</Stack>

variant, size, color and radius mean the same thing on every component, and Group / Stack replace hand-written flexbox. loading, withAsterisk and error are states the component already renders — reimplementing them costs the built-in accessibility wiring.


Pattern 3: Overlays and compound components

const [opened, { open, close }] = useDisclosure(false);

<Modal opened={opened} onClose={close} title="Confirm action" centered>
  <Button onClick={close}>Cancel</Button>
</Modal>;

Overlays are controlled by opened / onClose, and useDisclosure is the handler set they expect. Compound components — Menu.Target / Menu.Dropdown, Tabs.Tab / Tabs.Panel, Accordion.Item — pair their parts by value, so reordering the list cannot desync them.

Full code: examples/components.md


Pattern 4: Styling — the four routes

// Style props: theme values, not CSS values
<Box p="md" bg="blue.1" c="blue.9" fz="sm" w={{ base: "100%", sm: 400 }} />

// Styles API: named inner elements
<TextInput classNames={{ input: classes.input, label: classes.label }} />
<TextInput styles={{ input: { backgroundColor: "var(--mantine-color-blue-0)" } }} />
/* your-component.module.css */
.card {
  padding: var(--mantine-spacing-md);
  background: light-dark(
    var(--mantine-color-white),
    var(--mantine-color-dark-6)
  );

  @mixin hover {
    box-shadow: var(--mantine-shadow-md);
  }
  @mixin smaller-than $mantine-breakpoint-sm {
    padding: var(--mantine-spacing-sm);
  }
}

classNames takes classes and so supports pseudo-classes; styles takes inline objects and does not. Both target the same named selectors, listed per component in reference.md.

Full code: examples/core.md


Pattern 5: Theming

const theme = createTheme({
  colors: {
    brand: [...BRAND_COLORS], // exactly 10, index 0 lightest
    surface: virtualColor({ name: "surface", dark: "dark", light: "gray" }),
  },
  primaryColor: "brand",
  primaryShade: { light: 6, dark: 8 },
  components: {
    Button: Button.extend({ defaultProps: { variant: "filled", size: "sm" } }),
  },
});

primaryShade picks a different index per scheme, so one colour name reads correctly in both. virtualColor goes further and swaps the whole palette. Component.extend() sets defaults for every instance and is type-checked against that component's props.

Full code: examples/theming.md


Pattern 6: Colour scheme

const { setColorScheme } = useMantineColorScheme();
const computed = useComputedColorScheme("light");

<ActionIcon
  onClick={() => setColorScheme(computed === "dark" ? "light" : "dark")}
>
  {computed === "dark" ? <SunIcon /> : <MoonIcon />}
</ActionIcon>;

Read the scheme through useComputedColorScheme, not through useMantineColorScheme().colorScheme. The second returns the literal setting, which is "auto" for the default — so a toggle branching on it treats a dark system as light. lightHidden and darkHidden cover the render-one-or-the-other case without a hook at all.

Full code: examples/theming.md


Pattern 7: Forms

const form = useForm({
  mode: "uncontrolled",
  initialValues: { email: "", termsAccepted: false },
  validate: { email: (v) => (/^\S+@\S+$/.test(v) ? null : "Invalid email") },
});

<TextInput label="Email" withAsterisk key={form.key("email")} {...form.getInputProps("email")} />
<Checkbox label="I accept" key={form.key("termsAccepted")}
  {...form.getInputProps("termsAccepted", { type: "checkbox" })} />

getInputProps binds value, change handler and error in one spread; { type: "checkbox" } switches it to checked. mode: "uncontrolled" is the default and keeps keystrokes out of React's render path — which is why form.key() is required alongside it, to give React a key that survives the value changing outside its knowledge. Nested paths use dot notation (address.city, members.0.name), and list items move through insertListItem / removeListItem.

Full code, including schema resolvers: examples/forms.md


Pattern 8: Hooks

const [opened, { open, close, toggle }] = useDisclosure(false);
const [debounced] = useDebouncedValue(search, 300);
const isMobile = useMediaQuery("(max-width: 48em)");
const ref = useClickOutside(() => close());
const clipboard = useClipboard({ timeout: 2000 }); // clipboard.copied stays true for the timeout

useDebouncedValue and useClickOutside own their own cleanup. useMediaQuery returns undefined during server rendering rather than guessing, so branch on that third state instead of treating it as false.


Pattern 9: Notifications

<Notifications position="top-right" />; // once, inside MantineProvider

const id = notifications.show({
  loading: true,
  message: "Uploading…",
  autoClose: false,
});
notifications.update({
  id,
  loading: false,
  message: "Uploaded",
  color: "green",
  autoClose: 3000,
});

The renderer mounts once; the API is imperative and callable from anywhere, including outside React. Update by the returned id rather than showing a second notification.

Full code: examples/components.md


Pattern 10: Dates

import "@mantine/dates/styles.css";

<DatesProvider settings={{ locale: "en", firstDayOfWeek: 0 }}>
  <DatePickerInput
    type="range"
    label="Date range"
    value={range}
    onChange={setRange}
  />
</DatesProvider>;

Separate package, separate stylesheet, dayjs as a peer. type (default / range / multiple) changes the value's shape rather than the component.

Full code: examples/components.md

</patterns>

<red_flags>

Red flags

Breaks at runtime:

  • No @mantine/core/styles.css import — components render as unstyled markup with no error
  • No postcss-preset-mantine — @mixin, light-dark() and rem() reach the browser as invalid CSS and are dropped, so responsive and dark-mode rules silently do nothing
  • A package's own stylesheet left out — @mantine/dates/styles.css, @mantine/notifications/styles.css and the rest are separate imports
  • createStyles — removed in v7
  • A custom colour with fewer than ten shades — a TypeScript error, since the palette type is a 10-tuple
  • createTheme(...) inline in the JSX — a new theme object every render, re-rendering every consumer

Surprising behaviour:

  • useMantineColorScheme().colorScheme returns "auto" when that is the setting, so a toggle branching on it misreads a dark system as light — useComputedColorScheme resolves it
  • form.key() is not optional in uncontrolled mode; without it inputs keep stale values after a reset
  • Style props take theme values, not CSS values: p="md" is theme.spacing.md, and color="blue.6" is shade 6 of the palette rather than a CSS colour
  • The PostCSS breakpoint variables are a second copy of theme.breakpoints — change one and the CSS and the JS disagree with nothing to catch it
  • useMediaQuery returns undefined during server rendering, which is a third state rather than false
  • light-dark() here is compiled by the PostCSS preset rather than the native CSS function
  • The style prop reaches only the outermost element and takes raw CSS values, so a colour written there is frozen against the colour scheme — inner elements need classNames or styles, and a theme-aware colour needs bg / c or a --mantine-color-* variable
  • v8 shipped in May 2025. @mantine/dates moved from Date objects to "YYYY-MM-DD" strings, DatesProvider dropped timezone, and CodeHighlight dropped highlight.js. This skill documents v7; check the migration guide before upgrading.

</red_flags>

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

See all skills by agents-inc

Report a problem with this skill