antd-style-to-tailwind
/antd-style-to-tailwind
Przenosi komponenty antd-style z createStyles na klasy Tailwind CSS z obsługą trybu ciemnego, zgodnie ze strategią projektu.
Ocena i bezpieczeństwo
- Pliki w paczce
- 2
- Skrypty
- 0
- Komentarze HTML
- 0
- Adresy URL w SKILL.md
- 0
Brak sygnałów. Repozytorium zmienione 25.10.2026, bez odwołań do wycofanych modeli i API.
Apache-2.0, otwarta: można używać i zmieniać, z zachowaniem licencji i informacji o zmianach.
Co robi i kiedy
Przenosi komponenty antd-style z createStyles na klasy Tailwind CSS z obsługą trybu ciemnego, zgodnie ze strategią projektu.
Oryginalny opis (EN)
Migrate existing antd-style CSS-in-JS (createStyles) components to Tailwind CSS utility classes with full dark mode support, following the project's style system transition strategy. Use when the user asks to migrate, convert, or rewrite antd-style components to Tailwind, or when working on new components in files that already use antd-style.
Jak działa
- When to apply
- Prerequisites
- Migration Steps
- Dark Mode Patterns
- React patterns to fix during migration
- Shared base class pattern
- Checklist before finishing
- Additional resources
Podgląd SKILL.md
---
name: antd-style-to-tailwind
description: Migrate existing antd-style CSS-in-JS (createStyles) components to Tailwind CSS utility classes with full dark mode support, following the project's style system transition strategy. Use when the user asks to migrate, convert, or rewrite antd-style components to Tailwind, or when working on new components in files that already use antd-style.
---
# antd-style → Tailwind CSS Migration
## When to apply
- User asks to migrate / rewrite / convert a component's styles to Tailwind
- A new feature is being built in a file that currently uses `createStyles`
- User references `styles.ts`, `createStyle`, `useXxxStyles`, or `cx()`
- Component contains antd `Button` — replace with shadcn Button during migration
## Prerequisites
Always read these files before starting:
- The target component (`index.tsx` or equivalent)
- Its `styles.ts`
- `tailwind.config.js` and `src/index.css` if token mapping is unclear
## Migration Steps
### 1. Audit the `styles.ts`
Identify:
- Styles used **only** by this component → remove from `styles.ts`
- Styles shared with **other** components → keep `createStyles` for those
Also check if the styles file is imported anywhere else before deleting:
```bash
rg "from.*ComponentName/styles" --type tsx --type ts
```
### 2. Replace `createStyles` wiring
```diff
- import { useXxxStyles } from "./styles"
+ import { cn } from "@/opensource/lib/utils"
const targetComponent = observer((props) => {
- const { styles, cx } = useXxxStyles()
```
Use `cn()` everywhere `cx()` was used.
Do **not** build class strings with `+` concatenation. Prefer:
```tsx
const segmentedClassName = cn(
"rounded-md border border-border p-1",
"[&_.magic-segmented-group]:gap-0.5",
"[&_.magic-segmented-item-label]:text-xs",
)
```
### 3. Z-index semantic tokens
Prefer semantic z-index classes over arbitrary `z-[N]` values.
| Value | Tailwind class | CSS variable |
| ----- | -------------- | -------------------- |
| 1000 | `z-tooltip` | `--z-index-tooltip` |
| 1000 | `z-popup` | `--z-index-popup` |
| 1000 | `z-dropdown` | `--z-index-dropdown` |
| 1000 | `z-dialog` | `--z-index-dialog` |
| 1000 | `z-drawer` | `--z-index-drawer` |
Choose by context: use `z-tooltip` for tooltips, `z-dropdown` for menus, `z-dialog` for modals.
### 4. Translate CSS properties
| CSS | Tailwind |
| ---------------------------------------- | -------------------------------------- |
| `width: 100%` | `w-full` |
| `width: fit-content` | `w-fit` |
| `height: fit-content` | `h-fit` |
| `padding: 12px 0` | `py-3` |
| `padding: 10px` | `p-2.5` |
| `padding: 6px` | `p-1.5` |
| `padding: 4px 8px` | `px-2 py-1` |
| `padding: 4px 6px` | `px-1.5 py-1` |
| `padding: 0 4px` | `px-1` |
| `padding: 0 6px` | `px-1.5 py-0` |
| `margin-top: 6px` | `mt-1.5` |
| `margin-top: 10px` | `mt-2.5` |
| `margin-left: auto` | `ml-auto` |
| `display: flex` | `flex` |
| `display: inline-flex` | `inline-flex` |
| `flex-direction: column` | `flex-col` |
| `align-items: center` | `items-center` |
| `align-self: flex-end` | `self-end` |
| `justify-content: flex-end` | `justify-end` |
| `gap: 4px` | `gap-1` |
| `gap: 6px` | `gap-1.5` |
| `gap: 10px` | `gap-2.5` |
| `font-size: 12px; line-height: 16px` | `text-xs leading-4` |
| `font-size: 10px; line-height: 13px` | `text-[10px] leading-[13px]` |
| `font-size: 14px; line-height: 1.4` | `text-sm leading-[1.4]` |
| `font-weight: 600` | `font-semibold` |
| `font-weight: 400` | `font-normal` |
| `cursor: pointer` | `cursor-pointer` |
| `cursor: default` | `cursor-default` |
| `cursor: not-allowed` | `cursor-not-allowed` |
| `position: relative` | `relative` |
| `position: absolute` | `absolute` |
| `flex: none` | `flex-none` |
| `flex-shrink: 1` | `shrink` (default, often omit) |
| `white-space: nowrap` | `whitespace-nowrap` |
| `white-space: pre-wrap` | `whitespace-pre-wrap` |
| `overflow: hidden` | `overflow-hidden` |
| `text-overflow: ellipsis` | `text-ellipsis` |
| `min-width: 0` | `min-w-0` |
| `border-radius: 8px` | `rounded-md` (= 8px via `--radius-md`) |
| `border-radius: 12px` | `rounded-[12px]` |
| `border-radius: 0 4px 4px 0` | `rounded-r-[4px]` |
| `box-shadow: 0 1px 2px rgba(0,0,0,0.05)` | `shadow-sm` |
### 5. Map design tokens
Prefer semantic Tailwind vars — they auto-adapt to dark mode.
| antd-style token | Tailwind class | Notes |
| --------------------------------------------- | ------------------------------------------ | ---------------------------------- |
| `token.magicColorUsages.text[0]` | `text-foreground` | primary text |
| `token.magicColorUsages.text[1]` | `text-foreground/80` | slightly dimmed |
| `token.magicColorUsages.text[2]` | `text-muted-foreground` | secondary text |
| `token.magicColorUsages.text[3]` | `text-foreground/35` | disabled/placeholder |
| `token.magicColorUsages.fill[0]` | `bg-fill` | hover fill (subtle) |
| `token.magicColorUsages.fill[1]` | `bg-fill-secondary` | active fill (stronger) |
| `token.magicColorUsages.fill[2]` | `bg-black/[0.13]` + `dark:bg-white/[0.13]` | deep active fill |
| `token.magicColorUsages.border` | `border-border` | |
| `token.magicColorUsages.bg[0..4]` | `bg-background` | all white in light |
| `token.magicColorUsages.warning.default` | `text-orange-500` | ⚠️ orange, not amber |
| `token.magicColorUsages.warningLight.default` | `bg-orange-50` | ⚠️ orange-0 |
| `token.magicColorUsages.danger.default` | `text-destructive` | uses Tailwind semantic |
| `token.magicColorUsages.dangerLight.default` | `bg-destructive/10` | prefer slash opacity |
| `token.magicColorUsages.primary.default` | `text-primary` | brand blue |
| `token.magicColorUsages.primaryLight.default` | `bg-primary-10` | ⚠️ Use not `bg-primary/10` |
| `token.magicColorUsages.success.default` | `text-green-500` | |
| `token.magicColorUsages.successLight.default` | `bg-green-50` | |
| `token.magicColorUsages.info.default` | `text-blue-500` | |
| `token.magicColorUsages.infoLight.default` | `bg-blue-50` | |
| `token.magicColorUsages.tertiary.default` | `text-muted-foreground` | |
| `token.magicColorUsages.disabled.bg` | `bg-gray-200` + `dark:bg-white/10` | |
| `token.magicColorUsages.disabled.text` | `text-foreground/35` | |
| `token.magicColorScales.grey[0]` | `bg-muted` / `bg-gray-50` | |
| `token.magicColorScales.grey[2]` | `border-border` (preferred) | |
| `token.magicColorScales.grey[9]` | `text-foreground` | |
| `token.borderRadiusSM` (4px) | `rounded-[4px]` | use `[4px]` not `rounded-sm` (2px) |
**Project-specific opacity tokens** (from `src/index.css`):
- Primary light token keeps semantic suffix (`bg-primary-10`) due mixed light/dark opacity mapping
- Destructive opacity should use slash syntax (`bg-destructive/10`, `bg-destructive/20`)
| Avoid | Use | CSS variable |
| ------------------------------ | --------------------------------------------------- | ----------------------------------------------------------- |
| `bg-primary/10` | `bg-primary-10` | `--custom-primary-10-dark-primary-20` (light 10%, dark 20%) |
| `bg-destructive-10` | `bg-destructive/10` | `--destructive` + opacity |
| `bg-destructive-20` | `bg-destructive/20` | `--destructive` + opacity |
| `border-gray-500/10` (outline) | `border-[var(--custom-outline-10-dark-outline-20)]` | `--custom-outline-10-dark-outline-20` |
### 6. Map hardcoded hex colors
| Hex | Tailwind equivalent |
| --------------------- | ------------------------------------------------ |
| `#171717` | `text-foreground` |
| `#e5e5e5` (border) | `border-border` |
| `#9ca3af` | `text-gray-400` / `border-gray-400` |
| `#3b82f6` | `text-blue-500` |
| `#ebf2fe` | `bg-[#ebf2fe]` + dark: `dark:bg-blue-500/10` |
| `#f5f6f7` | `bg-[#f5f6f7]` + dark: `dark:bg-white/10` |
| `#f9f9f9` | `bg-gray-50` + dark: `dark:bg-white/5` |
| `#ffffff` (surface) | `bg-white` + dark: `dark:bg-card` |
| `rgba(46,47,56,0.09)` | `bg-black/[0.09]` + dark: `dark:bg-white/[0.09]` |
### 7. Pseudo-elements
`::after` / `::before` with decorative content:
```tsx
// Before (antd-style)
wrapper: css`
position: relative;
&::after {
content: " ";
position: absolute;
left: 11px;
top: 0;
width: 1px;
height: 100%;
border-left: 1px dashed ${token.magicColorScales.grey[2]};
}
`
// After (Tailwind)
;("relative after:content-[''] after:absolute after:left-[11px] after:top-0 after:w-px after:h-full after:border-l after:border-dashed after:border-border")
```
### 8. Child selectors
| CSS pattern | Tailwind arbitrary variant |
| -------------------------------------- | -------------------------- |
| `& p { margin-bottom: 0 }` | `[&_p]:mb-0` |
| `& p { color: X !important }` | `[&_p]:!text-X` |
| `& p { font-size: 12px !important }` | `[&_p]:!text-xs` |
| `& p { line-height: 16px !important }` | `[&_p]:!leading-4` |
| `svg { color: X }` | `[&_svg]:text-X` |
### 9. CSS animations
| antd-style keyframe | Tailwind |
| ----------------------------- | ----------------------------------------------------------------------------- |
| Custom `rotate(0 → 360)` spin | `animate-spin` (1s linear infinite) |
| Custom fade-in / slide | Check `tailwind.config.js` `animation` section for project-defined animations |
### 10. Replace antd layout components
antd layout primitives (`Flex`, `Space`, `Row`, `Col`) must be replaced with plain `<div>` + Tailwind.
#### `Flex` → `div`
| antd Flex prop | Tailwind equivalent |
| ------------------------- | --------------------------------- |
| `<Flex>` | `<div className="flex">` |
| `<Flex vertical>` | `<div className="flex flex-col">` |
| `gap={4}` | `gap-1` |
| `gap={6}` | `gap-1.5` |
| `gap={8}` | `gap-2` |
| `gap={10}` | `gap-2.5` |
| `gap={12}` | `gap-3` |
| `gap={16}` | `gap-4` |
| `gap={20}` | `gap-5` |
| `gap={24}` | `gap-6` |
| `align="center"` | `items-center` |
| `align="start"` | `items-start` |
| `align="end"` | `items-end` |
| `justify="center"` | `justify-center` |
| `justify="space-between"` | `justify-between` |
| `justify="flex-end"` | `justify-end` |
| `wrap` / `wrap="wrap"` | `flex-wrap` |
| `flex={1}` (child) | `flex-1` on the child |
```diff
- import { Flex } from "antd"
- <Flex vertical gap={10}>
- <span>…</span>
- <Flex gap={8}>…</Flex>
- </Flex>
+ <div className="flex flex-col gap-2.5">
+ <span>…</span>
+ <div className="flex gap-2">…</div>
+ </div>
```
#### `Space` → `div`
```diff
- <Space size={8} direction="vertical">
+ <div className="flex flex-col gap-2">
```
### 10.5 Replace `MagicScrollBar` with shadcn `ScrollArea`
When migrating legacy list areas:
```diff
- import MagicScrollBar from "@/opensource/components/base/MagicScrollBar"
+ import { ScrollArea } from "@/opensource/components/shadcn-ui/scroll-area"
```
Map old SimpleBar content padding to `viewportClassName`:
```diff
- <MagicScrollBar className="h-full w-full [&_.simplebar-content]:px-2.5 [&_.simplebar-content]:pb-2.5">
+ <ScrollArea className="min-h-0 h-full w-full" viewportClassName="px-2.5 pb-2.5">
```
**Critical height-chain rule (flex layouts):**
- Every ancestor from panel body to `ScrollArea` must provide a calculable height
- Typical safe chain: `flex-1 min-h-0 overflow-hidden` on parent + `min-h-0 h-full` on `ScrollArea`
- Avoid wrapping `ScrollArea` with components that add an extra auto-height layer (for example spinner wrappers) unless that wrapper and its direct child are both `h-full`
If loading overlay breaks scrolling, prefer rendering loading as a branch:
```tsx
{
loading ? (
<div className="flex h-full w-full items-center justify-center">{/* spinner */}</div>
) : (
<ScrollArea className="min-h-0 h-full w-full" viewportClassName="px-2.5 pb-2.5">
{/* list */}
</ScrollArea>
)
}
```
Keep existing `data-testid` values on list / loading / empty nodes when replacing containers.
### 11. Replace antd Button with shadcn Button
**When migrating a component that uses antd `Button`, replace it with shadcn Button** from
`@/opensource/components/shadcn-ui`.
#### Import change
```diff
- import { Button } from "antd"
+ import { Button } from "@/opensource/components/shadcn-ui"
```
#### antd → shadcn mapping
| antd Button | shadcn Button |
| ----------------- | ----------------------------------------------------------------------- |
| `type="primary"` | `variant="default"` (or omit, default) |
| `type="default"` | `variant="outline"` or `variant="secondary"` |
| `type="text"` | `variant="ghost"` |
| `type="link"` | `variant="link"` |
| `danger` | `variant="destructive"` |
| `size="large"` | `size="lg"` |
| `size="small"` | `size="sm"` |
| `icon={<Icon />}` | Put icon as child: `<Button><Icon /> Text</Button>` |
| `loading` | Use `disabled` + loading spinner as child, or a loading state component |
#### Example migration
```diff
- import { Button } from "antd"
- import { IconDownload } from "@tabler/icons-react"
+ import { Button } from "@/opensource/components/shadcn-ui"
+ import { IconDownload } from "@tabler/icons-react" // or lucide-react
- <Button type="primary" icon={<IconDownload size={20} />} onClick={handleDownload}>
- {t("detail.downloadFile")}
- </Button>
+ <Button onClick={handleDownload} size="sm">
+ <IconDownload className="size-5" />
+ {t("detail.downloadFile")}
+ </Button>
```
shadcn Button has built-in `[&_svg]:size-4`; use `className="size-5"` on the icon for 20px.
Remove any `className` overrides that were needed for antd — shadcn Button uses Tailwind and
supports `className` for minor tweaks only.
### 11.5 Replace antd Input with shadcn Input
When migrating a component that uses antd `Input`, replace it with shadcn Input from
`@/opensource/components/shadcn-ui/input`.
#### Import change
```diff
- import { Input } from "antd"
+ import { Input } from "@/opensource/components/shadcn-ui/input"
```
#### Prefix icon migration
Antd `prefix` prop is not used in shadcn Input. Wrap Input with a relative container and render
the icon absolutely:
```tsx
<div className="relative w-full">
<IconSearch
size={16}
className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
/>
<Input
value={searchText}
onChange={(event) => setSearchText(event.target.value)}
className="pl-9"
placeholder={t("mcp.panel.searchPlaceholder")}
/>
</div>
```
#### Notes
- Keep `data-testid` unchanged during migration
- Preserve placeholder and controlled value logic
- Use fixed width wrapper only when desktop layout needs it (for example `w-[240px]`)
### 12. Antd component overrides (non-Button)
When applying Tailwind to **other** antd components (`Dropdown`, `Modal`, etc.), antd's CSS
specificity requires `!important` for most visual properties. Layout utilities like `flex-none`
do NOT need `!` since they're additive.
**Workspace rule (critical):** for internal antd class overrides, use `.magic-*` prefix,
not `.ant-*`.
- ✅ `"[&_.magic-alert]:text-xs"`
- ❌ `"[&_.ant-alert]:text-xs"`
```tsx
// Dropdown overlayClassName targeting internal antd classes
overlayClassName="[&_.magic-dropdown-menu]:!p-[6px] [&_.magic-dropdown-menu]:flex
[&_.magic-dropdown-menu]:flex-col [&_.magic-dropdown-menu]:gap-1
[&_.magic-dropdown-menu-item]:!p-0"
```
**Rule of thumb for `!`:** Does antd set this property by default? If yes → `!`. If it's purely
additive (flex-none, mt-2.5, self-end) → no `!` needed.
### 13. Interactive states (hover / active / focus / disabled)
Always add all four states when styling interactive elements. Missing any state causes visible
regressions (e.g., a button that darkens on hover but ignores keyboard focus).
#### State token mapping
| State | Token equivalent | Tailwind class | Notes |
| ------------------- | ---------------- | ------------------------------------------------------------------------- | --------------------------- |
| hover bg | `fill[0]` | `hover:bg-fill` | semantic, auto dark-mode |
| hover bg (stronger) | `fill[1]` | `hover:bg-fill-secondary` | for already-filled surfaces |
| active bg | `fill[1]` | `active:bg-fill-secondary` | slightly darker than hover |
| hover text | `text[0]` | `hover:text-foreground` | keeps text readable |
| focus ring | `ring` CSS var | `focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring` | keyboard only |
| disabled | — | `disabled:pointer-events-none disabled:opacity-50` | always include |
#### Ghost button (bg-background + border)
```tsx
// Canonical ghost button pattern — always use cn()
const ghostButton = cn(
"inline-flex items-center cursor-pointer rounded-lg",
"bg-background text-foreground border border-border",
"hover:bg-fill hover:text-foreground",
"active:bg-fill-secondary",
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
"disabled:pointer-events-none disabled:opacity-50",
)
```
#### Filled button (bg-primary)
```tsx
const primaryButton = cn(
"bg-primary text-primary-foreground",
"hover:bg-primary/90",
"active:bg-primary/80",
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
"disabled:pointer-events-none disabled:opacity-50",
)
```
#### Filled muted button (bg-muted / bg-fill)
```tsx
const mutedButton = cn(
"bg-fill text-foreground",
"hover:bg-fill-secondary hover:text-foreground",
"active:bg-black/[0.13] dark:active:bg-white/[0.13]",
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
"disabled:pointer-events-none disabled:opacity-50",
)
```
#### Shared const pattern (for custom div buttons)
When building custom button-like elements (not shadcn Button), extract to a module-level const:
```tsx
// Outside component — for div/span that acts as button
const actionButtonBase = cn(
"inline-flex h-6 items-center gap-1 px-2.5 py-1",
"cursor-pointer rounded-lg text-xs leading-4",
"bg-background text-foreground border border-border",
"hover:bg-fill hover:text-foreground",
"active:bg-fill-secondary",
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
"disabled:pointer-events-none disabled:opacity-50",
)
// For shadcn Button — use variant/size props, className for minor tweaks only
<Button variant={isActive ? "default" : "outline"} size="sm">
```
## Dark Mode Patterns
Add `dark:` variants for all hardcoded light-specific colors.
| Light | Dark | Use case |
| ------------------------ | ----------------------------- | ------------------------------ |
| `bg-white` | `dark:bg-card` | surface / card bg |
| `bg-orange-50` | `dark:bg-orange-500/20` | warning/warningLight bg |
| `text-orange-500` | `dark:text-orange-400` | warning text |
| `[&_p]:!text-orange-500` | `dark:[&_p]:!text-orange-400` | warning p override |
| `bg-green-50` | `dark:bg-green-500/20` | success bg |
| `text-green-500` | `dark:text-green-400` | success text |
| `bg-blue-50` | `dark:bg-blue-500/20` | info/secondary bg |
| `text-blue-500` | `dark:text-blue-400` | info/secondary text |
| `bg-[#ebf2fe]` | `dark:bg-blue-500/10` | finished badge bg (hardcoded) |
| `bg-[#f5f6f7]` | `dark:bg-white/10` | suspended badge bg (hardcoded) |
| `text-gray-400` | `dark:text-gray-500` | muted icon/label |
| `bg-gray-50` | `dark:bg-white/5` | subtle list item bg |
| `!bg-white` | `dark:!bg-card` | antd button/surface bg |
| `bg-black/[0.09]` | `dark:bg-white/[0.09]` | fill[1] dark overlay |
| `bg-black/[0.13]` | `dark:bg-white/[0.13]` | fill[2] darker overlay |
Semantic Tailwind variables (`bg-background`, `border-border`, `text-foreground`, `bg-muted`,
`bg-fill`, `bg-fill-secondary`) are **already dark-mode aware** — no `dark:` prefix needed.
### Dark Mode Verification Checklist
After migration, **verify dark mode styles are complete**:
1. **Scan for hardcoded light colors without `dark:`**
- `bg-white`, `bg-gray-50`, `bg-orange-50`, `bg-green-50`, `bg-blue-50`, `bg-amber-50`
- `text-gray-400`, `text-gray-500`, `text-orange-500`, `text-green-500`, `text-blue-500`
- `bg-[#...]`, `text-[#...]` (hex values)
- `border-gray-200`, `border-gray-300`
- Each of these **must** have a matching `dark:` variant.
2. **Verify semantic tokens**
- Prefer `bg-background`, `text-foreground`, `border-border`, `bg-muted`, `bg-fill` — these auto-adapt.
- If using fixed colors (e.g. `bg-white`), always pair with `dark:bg-card` or equivalent.
3. **Interactive elements**
- `hover:`, `active:` states using light-only colors need `dark:` variants.
- Example: `hover:bg-gray-100` → add `dark:hover:bg-white/10`.
4. **Manual verification**
- Toggle dark mode in the app and visually inspect the migrated component.
- Check: backgrounds, text contrast, borders, shadows, focus rings.
- Ensure no "invisible" text or low-contrast elements in dark mode.
5. **Search pattern for audit**
```bash
# Find light-only color classes — verify each has a dark: variant in same className
rg "bg-(white|gray-50|orange-50|green-50|blue-50|amber-50)" --type tsx path/to/component
rg "text-(gray-[34]00|orange-500|green-500|blue-500)" --type tsx path/to/component
```
## React patterns to fix during migration
### Prefer proper types over eslint-disable / `as any`
**When passing props to child components**, fix type errors by using proper interfaces instead of
`as any` or `eslint-disable-next-line @typescript-eslint/no-explicit-any`.
1. **Export the child's props interface** from the child component:
```diff
- interface PlaybackTabContentProps {
+ export interface PlaybackTabContentProps {
```
2. **Use that interface as the parent's prop type**:
```diff
- playbackProps?: Record<string, unknown>
+ playbackProps?: PlaybackTabContentProps
```
3. **Remove type assertions** — spread directly without `as any`:
```diff
- <PlaybackTabContent {...(playbackProps as any)} />
+ <PlaybackTabContent {...playbackProps} />
```
4. **Simplify property access** — no cast needed:
```diff
- (playbackProps as Record<string, unknown>)?.isFullscreen === true
+ playbackProps?.isFullscreen === true
```
Reserve `eslint-disable` only for cases where proper typing is impractical (e.g. MobX store data
with loose typing in `.map()` callbacks). Prefer exporting interfaces and typing props correctly.
### Object initializer in deps causes stale closure warnings
The pattern `const x = thing || {}` creates a new object reference every render, causing
ESLint to warn about unstable `useCallback`/`useMemo` deps. Wrap it in `useMemo`:
```diff
- const fileData = tool?.detail?.data || {}
+ const fileData = useMemo(() => tool?.detail?.data || {}, [tool?.detail?.data])
```
### `@ts-ignore` vs `@ts-expect-error`
- `@ts-ignore` — suppresses the error silently; **ESLint bans it**.
- `@ts-expect-error` — requires a real error to exist; **errors if the line is clean**.
Migration rule:
1. Replace `// @ts-ignore` with `// @ts-expect-error` and add a brief reason.
2. Run lint. If you get "Unused '@ts-expect-error' directive" → the line is already clean, just
**delete** the comment entirely.
```diff
- // @ts-ignore
+ // @ts-expect-error - result type does not expose data.file_id
const fileId = result?.data?.file_id || result?.currentFileId
```
### `any` on JSX `.map()` callbacks over store data (last resort)
When iterating over data from a MobX/Zustand store with loose typing, **first try** to add proper
types (e.g. define `StoreItem` interface, use `as StoreItem` where the parent builds the data).
Use `eslint-disable` only when proper typing is impractical (e.g. store shape is dynamic or
external).
```tsx
{
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
}
{
items?.map((item: any) => (
<Child data={item.attrs as ExpectedType} key={getUniqueId(item.attrs as ExpectedType)} />
))
}
```
> ⚠️ The `eslint-disable-next-line` comment must be on the line **immediately before** the
> `{items?.map(` expression — not before the wrapping `{condition && (` wrapper.
## Shared base class pattern
When multiple elements share the same base styles, extract into a `const` outside the component:
```tsx
// Before (in createStyles)
box: css`border-radius: 12px; display: inline-flex; padding: 4px 8px; ...`
// After (module-level const)
const boxBase = "rounded-[12px] inline-flex px-2 py-1 items-center text-xs gap-1 [&_p]:mb-0"
// Usage
<span className={cn(boxBase, "bg-amber-50 text-amber-500 dark:bg-amber-500/10 dark:text-amber-400")}>
```
## Checklist before finishing
- [ ] All `styles.xxx` references replaced with Tailwind classes
- [ ] `useXxxStyles` import and call removed from component
- [ ] `createStyles` export removed from `styles.ts` (if no other consumers)
- [ ] `keyframes` import removed from `styles.ts` if unused
- [ ] `styles.ts` file deleted after confirming no other imports
- [ ] `cn()` imported from `@/opensource/lib/utils`
- [ ] No className `+` concatenation; use `cn(...)` instead
- [ ] **antd Button** replaced with shadcn `Button` from `@/opensource/components/shadcn-ui`
- [ ] **antd Input** replaced with shadcn `Input` from `@/opensource/components/shadcn-ui/input`
- [ ] If `MagicScrollBar` was replaced, `ScrollArea` uses `viewportClassName` and height-chain (`min-h-0` + `h/full`) is verified
- [ ] **Dark mode verification**: All hardcoded light colors have `dark:` variants; prefer semantic tokens (`bg-background`, `text-foreground`, etc.); manually verify in dark mode
- [ ] antd layout components (`Flex`, `Space`, `Row`, `Col`) replaced with `div` + Tailwind
- [ ] Unused antd imports removed after replacement
- [ ] Interactive elements have **all four states**: `hover:` / `active:` / `focus-visible:` / `disabled:`
- [ ] Repeated button/link classes extracted to a module-level `const` (DRY)
- [ ] `token.borderRadiusSM` translated to `rounded-[4px]`, not `rounded-sm`
- [ ] Object initializers used as hook deps wrapped in `useMemo`
- [ ] `@ts-ignore` replaced with `@ts-expect-error` + reason, or deleted if no error exists
- [ ] **Type safety**: Props passed to children use proper interfaces (export from child, use in parent); avoid `as any` and `eslint-disable` for type errors
- [ ] `any` in JSX map callbacks used only as last resort; prefer proper types when feasible
- [ ] `!` prefix on antd overrides applied correctly (visual props only, not layout utilities)
- [ ] Antd override selectors use `.magic-*` class prefix, not `.ant-*`
- [ ] Lints pass (`pnpm lint`)
## Additional resources
- Color mapping details: [color-mapping.md](color-mapping.md)
Pliki w paczce (2)
- SKILL.md 32 KB
- color-mapping.md 12 KB
Wersje
W bazie nie ma innych skilli o nazwie antd-style-to-tailwind.
Identyczna treść występuje jeszcze w 1 innych folderach na GitHubie. Tu pokazujemy jedną, najlepiej ocenioną kopię.
Migawka bazy z 28.10.2026 · wersja paczki 3fe93a67736b (skrót SHA-256 całej paczki)
Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.
Podobne skille
- 100antd
Wspiera pracę z biblioteką Ant Design (antd): tworzenie komponentów, naprawianie błędów, sprawdzanie API, właściwości i tokenów, migracje wersji oraz analiza użycia antd w projekcie. - 100antd
Pomaga pracować z Ant Design (antd): pisanie komponentów, debugowanie, API, właściwości, tokeny, dema, migracje między wersjami i analiza użycia w projekcie. - 100tailwind-patterns
Dobiera wzorce Tailwind CSS v4 do stylowania komponentów, trybu ciemnego i integracji z systemem projektowym. - 100styling-with-tailwind
Dostarcza wzorce stylowania utility-first z Tailwind CSS 3.x: klasy narzędziowe, tailwind.config.js, responsywne układy i motywy ciemne. - 100tailwind
Stanowi bazę wiedzy o Tailwind CSS 4: klasy narzędziowe, konfiguracja, tokeny designu, układy responsywne i dark mode. - 100tailwind-best-practices
Podaje wzorce i konwencje Tailwind CSS: projekty responsywne, dark mode, style komponentów wielokrotnego użytku, konfiguracja i migracja z v3 do v4.