Migrate from Tailwind
To move a codebase from Tailwind to motif, translate utility classes into style props and port
tailwind.config into a theme. The scales line up — Tailwind's spacing and breakpoints are the
same shape as motif's token tree.
What maps directly
| Tailwind | motif |
|---|---|
flex gap-4 | display="flex" gap="$4" |
p-4 / px-3 / mt-2 | p="$4" / px="$3" / mt="$2" |
bg-white / text-gray-900 | bg="$colors.surface.base" / color="$colors.text.default" |
rounded-lg | borderRadius="$radii.lg" |
text-lg font-semibold | fontSize="$lg" fontWeight="$semibold" |
hover:bg-gray-100 | _hover={{ bg: '$colors.surface.muted' }} |
md:flex-row | flexDirection={{ base: 'column', md: 'row' }} |
dark:bg-black | a dark theme — see Theming |
tailwind.config theme | createTheme({ tokens: { … } }) |
Tailwind's numeric spacing scale (4 = 1rem) and motif's space scale are the same idea — a
named step, not a raw length. The $4 reference resolves through the theme exactly as p-4 runs
through the config.
Mismatches and gaps
- Arbitrary-value classes. Tailwind's
p-[13px]escape hatch becomes a literal prop value:p={13}orp="13px". motif accepts literals anywhere a token reference is valid. @apply. Tailwind's@applycomposes utilities inside a CSS rule. motif's equivalent isstyled()— bundle the recurring set of props into one named component.- The plugin ecosystem.
@tailwindcss/forms,@tailwindcss/typography, and other plugins have no motif equivalent. motif's form primitives cover the forms case; prose styling is astyled()component you own. - Dark mode. Tailwind's
dark:variant toggles a class; motif models light and dark as two themes and switches the active one. The result is the same; the mechanism is a theme, not a per-utility variant. - JIT and purging. Tailwind ships a build step that scans class names. motif's CSS comes from the runtime or the compiler — there is no class-name scan, so no purge config and no safelist.
Migrate a component
A typical Tailwind component:
Becomes:
The responsive md:p-6 becomes a responsive object — p={{ base: '$4', md: '$6' }}. The grey
literals become semantic tokens; pick the theme path that carries the intent (surface,
text.muted) rather than transcribing the shade number.
Migrate the config
tailwind.config becomes a createTheme call. The theme object is the token tree.
This is the moment to add a semantic layer. Tailwind's gray-600 is a value; motif's
text.muted is a role. Naming the roles once, in the theme, is what lets a dark theme drop in
later without touching a component.
Migrate incrementally
A motif <Box className="…"> still passes className straight through, so a half-migrated
component can carry Tailwind classes and motif props at once. Convert file by file; uninstall
Tailwind when grep className turns up nothing but motif primitives.