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.
createStylesstyles.ts, createStyle, useXxxStyles, or cx()Button — replace with shadcn Button during migrationAlways read these files before starting:
index.tsx or equivalent)styles.tstailwind.config.js and src/index.css if token mapping is unclearstyles.tsIdentify:
styles.tscreateStyles for thoseAlso check if the styles file is imported anywhere else before deleting:
rg "from.*ComponentName/styles" --type tsx --type ts
createStyles wiring- 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:
const segmentedClassName = cn(
"rounded-md border border-border p-1",
"[&_.magic-segmented-group]:gap-0.5",
"[&_.magic-segmented-item-label]:text-xs",
)
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.
| 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 |
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):
bg-primary-10) due mixed light/dark opacity mappingbg-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 |
| 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] |
::after / ::before with decorative content:
// Before (antd-style)