Skip to main content

emotion-migrate

Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports from components.

Quellinformationen

Repository
metabase/metabase
Letzte Quellaktivität
15. Oktober 2026 um 12:13
Erkannte Sprache von SKILL.md
Englisch
Sterne
49.694
Forks
6.904

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
emotion-migrate
description
Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports from components.
# Emotion → Mantine + CSS Modules Migration Skill Migrate Emotion styled-components (`@emotion/styled`, `@emotion/react`) to Mantine layout components with style props and CSS modules. The goal is zero Emotion imports, zero inline styles, and maximum use of design system tokens. ## Priority Order (Strict) 1. **Mantine components + style props** — `Box`, `Flex`, `Stack`, `Group`, `Text`, `Title`, `Card`. This is the DEFAULT. Every CSS property must be checked against style props FIRST. 2. **CSS modules** (`.module.css`) — ONLY for properties that Mantine style props genuinely cannot express: pseudo-selectors (`:hover`, `:focus`, `::before`), `box-shadow`, `border` shorthand, `animation`/`@keyframes`, complex selectors, `cursor`, `pointer-events`, `overflow`, `text-overflow`, `white-space`, `transition`. 3. **Inline styles ONLY for dynamic values** — `style={{ }}` is allowed only for truly dynamic runtime values (e.g., computed widths, positions, data-driven colors). All static styles must use Mantine props or CSS modules. ### Mantine-First Decision Gate (CRITICAL) For EACH styled component, go through every CSS property and ask: "Can this be a Mantine style prop?" If yes → style prop. If no → CSS module. Do NOT dump an entire component into a CSS module just because one property needs it — split them. **Properties that ARE style props** (use these, not CSS modules): - `display` → `display` prop - `color` → `c` prop (`c="core-brand"`, `c="text-primary"`) - `background-color` → `bg` prop (`bg="background_page-primary"`) - `font-size` → `fz` prop (`fz="md"`) - `font-weight` → `fw` prop (`fw="bold"`) - `line-height` → `lh` prop (`lh="md"`) - `text-align` → `ta` prop (`ta="center"`) - `padding` (all variants) → `p`, `px`, `py`, `pt`, `pb`, `pl`, `pr` - `margin` (all variants) → `m`, `mx`, `my`, `mt`, `mb`, `ml`, `mr` - `width` → `w`, `min-width` → `miw`, `max-width` → `maw` - `height` → `h`, `min-height` → `mih`, `max-height` → `mah` - `flex` → `flex` prop (`flex="0 0 auto"`, `flex={1}`) - `gap` → `gap` prop (on Flex/Stack/Group) - `align-items` → `align` prop (on Flex/Stack/Group) - `justify-content` → `justify` prop (on Flex/Stack/Group) - `flex-direction` → `direction` prop (on Flex) - `flex-wrap` → `wrap` prop (on Flex) - `position` → `pos` prop - `top/right/bottom/left` → `top`, `right`, `bottom`, `left` props - `opacity` → `opacity` prop **Properties that NEED CSS modules** (no style prop equivalent): - `:hover`, `:focus`, `:active`, `::before`, `::after` (pseudo-selectors) - `box-shadow`, `border` (shorthand with color), `outline` - `cursor`, `pointer-events` - `overflow`, `text-overflow`, `white-space` - `animation`, `transition`, `transform` - `@media` queries (UNLESS it's simple responsive spacing/sizing — then use responsive syntax: `p={{ base: "md", lg: "xl" }}`) **Hybrid approach** — when a component needs BOTH, put style props on the Mantine component AND add a CSS module class for the rest: ```tsx <Flex className={S.root} /* for :hover, box-shadow, border */ align="center" /* style prop */ gap="sm" /* style prop */ p="md" /* style prop */ bg="background_page-primary" /* style prop */ > ``` ## CSS Module Conventions (Strict) ### Class Naming: camelCase All CSS module class names MUST use **camelCase**. This is the dominant convention across the codebase (~830 camelCase vs ~620 PascalCase classes), used consistently in Mantine UI components, and matches standard CSS module conventions. ```css /* CORRECT */ .root { } .settingsSection { } .dragHandle { } .closeIcon { } /* WRONG — do not use PascalCase or kebab-case */ .ItemRoot { } .settings-section { } ``` Modifier/state classes also use camelCase: ```css .selected { } .disabled { } .interactive { } .draggable { } ``` ### No Cascading — Direct Class Assignment Cascading selectors are **discouraged**. Instead of styling through parent-child relationships, assign a class directly to the element that needs styling. ```css /* WRONG — cascading/descendant selectors */ .root > input { } .root .label { } .container > div > span { } /* CORRECT — direct class on the target element */ .input { } .label { } .title { } ``` The only acceptable nesting patterns are: - **Pseudo-selectors on the same element**: `.item { &:hover { } }` - **Modifier composition**: `.item { &.selected { } }` - **Hover-reveal patterns** where a parent hover affects a child: `.root:hover .showOnHover { opacity: 1; }` — but only when structurally necessary (the child has no way to know about the parent's hover state) ### Import Alias Always import the CSS module as `S`: ```tsx import S from "./ComponentName.module.css"; ``` ## Step-by-Step Migration Process ### Step 1: Read and Understand Read the `.styled.tsx` file AND every component that imports from it. Understand: - Which styled components are used and where - Which props drive dynamic styles - Which styles are static vs conditional - Which styles can map directly to Mantine style props ### Step 2: Classify Each Styled Component For each styled component, apply the Mantine-First Decision Gate above. Then determine the migration target: | Emotion Pattern | Migration Target | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `styled.div` with only layout/spacing/color | `Box`, `Flex`, `Stack`, or `Group` with style props. **NO CSS module needed.** | | `styled.div` with flexbox column | `Stack` component with style props | | `styled.div` with flexbox row | `Flex` or `Group` component with style props | | `styled.span` / `styled.p` with color/weight/size | **`Text component="span"` with style props** (`c`, `fw`, `fz`). NO CSS module. | | `styled.div` with hover/focus/pseudo-selectors | **Hybrid**: Mantine component with style props for expressible properties + CSS module class for pseudo-selectors only | | `styled.div` with media queries (simple spacing/sizing) | **Responsive style props**: `p={{ base: "md", lg: "xl" }}`. NO CSS module. | | `styled.div` with media queries (complex/non-spacing) | CSS module for the media query parts, style props for the rest | | `styled.div` with animations/keyframes | CSS module for animation, style props for layout | | `styled(SomeComponent)` with only color/spacing/flex | **Wrap in Mantine component** with style props: `<Box c="core-brand" flex="0 0 auto"><Icon /></Box>`, or pass style props if the component accepts them | | `styled(SomeComponent)` with pseudo-selectors/complex styles | CSS module `className` on the component | | Dynamic props `styled.div<{ isActive: boolean }>` | Mantine style props for simple toggles (`c={active ? "core-brand" : "text-primary"}`), `cx()` with CSS module classes for complex state combinations involving pseudo-selectors | ### Step 3: Create CSS Module (if needed) Create `ComponentName.module.css` alongside the component file: ```css /* Use design system tokens — NEVER raw color/spacing values */ .root { border: 1px solid var(--mb-color-border-neutral); border-radius: var(--mantine-radius-md); background-color: var(--mb-color-background_page-primary); } /* Conditional states as separate classes, combined with cx() */ .active { background-color: var(--mb-color-core-brand); color: var(--mb-color-text-primary-inverse); } .disabled { color: var(--mb-color-text-disabled); pointer-events: none; } /* Hover/focus/pseudo-selectors — nest with & */ .interactive { cursor: pointer; &:hover { color: var(--mb-color-core-brand); background-color: var(--mb-color-background_surface-hover); } } /* Hover-reveal: acceptable parent→child nesting */ .root:hover .showOnHover { opacity: 1; } /* Responsive styles */ @media (--breakpoint-min-md) { .root { padding: var(--mantine-spacing-lg); } } ``` ### Step 4: Update the Component TSX ```tsx import cx from "classnames"; import CS from "metabase/css/core/index.css"; import { Box, Flex, Group, Stack, Text } from "metabase/ui"; import S from "./ComponentName.module.css"; // Dynamic props → cx() with conditional classes <Flex className={cx(S.root, { [S.active]: isActive, [S.disabled]: disabled, })} align="center" gap="sm" p="md" w="100%" > ``` ### Step 5: Delete the `.styled.tsx` File Remove the old styled file entirely. Remove all imports of it from other files. ### Step 6: Verify - Confirm zero `@emotion/styled` or `@emotion/react` imports remain in the migrated files - Confirm no static inline styles (dynamic runtime values are fine) - Confirm all colors use tokens, not raw hex/rgb values ## Design System Token Reference ### Mantine Style Props (use directly on components) **Spacing** (`p`, `px`, `py`, `pt`, `pb`, `pl`, `pr`, `m`, `mx`, `my`, `mt`, `mb`, `ml`, `mr`): - `"xs"` = 4px, `"sm"` = 8px, `"md"` = 16px, `"lg"` = 24px, `"xl"` = 32px - Custom: `rem(48)` for non-standard values (import `rem` from `metabase/ui`) **Dimensions** (`w`, `h`, `maw`, `mah`, `miw`, `mih`): - `"100%"`, `"100vh"`, `rem(400)`, etc. **Colors** (`c`, `bg`): - Text: `"text-primary"`, `"text-secondary"`, `"text-disabled"` - Background: `"background_page-primary"`, `"background_page-secondary"`, `"background_surface-hover"`
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen