Getting Started
This guide will help you get started with styled-cva.
Installation
Install styled-cva using your preferred package manager:
npm
bash npm i --save @styled-cva/react For other framework adapters:
- Solid:
@styled-cva/solid - Vue:
@styled-cva/vue - ReScript:
@styled-cva/rescript(see ReScript Guide)
Basic Usage
Create a styled component using template literals:
import tw from "@styled-cva/react";
const StaticButton = tw.button`
bg-primary rounded-xl cursor-pointer
`;
// Use it like a regular React component
<StaticButton>Click Me</StaticButton>;Variants
Use tw.button(base, config) (intrinsic CVA shorthand) for variant styles:
import tw from "@styled-cva/react";
const VariantButton = tw.button("btn-base-class", {
variants: {
// Variant keys starting with $ will not be sent to the DOM,
// this avoids extraneous props warning
$variant: {
primary: "btn-primary-class",
secondary: "btn-secondary-class",
},
},
});
// $variant is inferred to 'primary' | 'secondary'
<VariantButton $variant="primary">Click Me</VariantButton>;
// The actual element in the DOM will be:
// <button class="btn-primary-class">Click Me</button>tw.button.cva(base, config) is deprecated — same behavior as the shorthand above. Prefer calling the factory directly.
Variant keys starting with $ are transient props that won’t be passed to the
DOM element. This prevents React warnings about unknown props.
Default Props
Use .withProps() to set default props:
const StyledButton = tw.button("btn-base", {
variants: {
$variant: {
primary: "btn-primary",
secondary: "btn-secondary",
},
},
})
.withProps({
"data-testid": "my-button",
type: "button",
$variant: "primary", // Valid variant value
});
// The component now has default props applied
<StyledButton>Click Me</StyledButton>;
// User props override defaults
<StyledButton $variant="secondary" type="submit">
Submit
</StyledButton>;Polymorphic Components
Use the $as prop to render as different elements or components:
import Link from "next/link";
const Button = tw.button("btn", {
variants: {
$variant: {
primary: "btn-primary",
secondary: "btn-secondary",
},
},
});
// Works with known JSX elements
<Button $as="a" href="/some/url">
I'm a link that looks like a button
</Button>;
// Also with custom components
<Button $as={Link} href="/some/url">
I'm a link that looks like a button
</Button>;Styling Custom Components
Create a styled component from a custom component that accepts a className prop:
import tw from "@styled-cva/react";
const MyButton = ({ className }: { className: string }) => {
return <button className={className}>Hello</button>;
};
const StyledButton = tw(MyButton)`text-red-500`;
// Use it like a regular component
<StyledButton />;VSCode IntelliSense
Install the Tailwind CSS IntelliSense extension and add this to your workspace .vscode/settings.json:
{
"tailwindCSS.emmetCompletions": true,
"tailwindCSS.includeLanguages": {
"typescript": "javascript",
"typescriptreact": "javascript"
},
"tailwindCSS.experimental.classRegex": [
"tw`([^`]*)",
"tw\\.\\w+`([^`]*)`",
"tw\\([^)]*\\)`([^`]*)`",
["tw\\.\\w+\\s*\\(([^;]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"],
["tw\\.\\w+\\.cva\\s*\\(([^;]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"],
["tw\\([^)]+\\)\\s*\\(([^;]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"],
["cva\\(([^;]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"],
["cn\\(([^;]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"]
],
"editor.quickSuggestions": {
"strings": true
}
}Tuple patterns (the ["…", "…"] entries) need a capturing group around the
full call arguments — ([^;]*) — so IntelliSense sees the base string and every
variant value inside tw.span("base", { variants }). Matching only tw.span(
will not work. Reload the window after editing settings.
Preact
@styled-cva/react works under Preact via the standard preact/compat alias. Preact provides drop-in replacements for forwardRef and the React type stack that styled-cva relies on, so no styled-cva code changes are required — set up the alias in your bundler and use the package exactly as you would in React.
Install
npm
bash npm i --save preact @styled-cva/react Bundler alias
Vite
// vite.config.ts
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";
export default defineConfig({
plugins: [preact()],
resolve: {
alias: {
react: "preact/compat",
"react-dom": "preact/compat",
"react/jsx-runtime": "preact/jsx-runtime",
},
},
});@preact/preset-vite wires the alias for you; the explicit resolve.alias block above is shown for projects that don’t use the preset.
Usage
No changes from the React workflow:
import tw from "@styled-cva/react";
const Button = tw.button("rounded bg-blue-500 px-4 py-2 text-white", {
variants: {
$variant: {
primary: "bg-blue-500",
ghost: "bg-transparent text-blue-500",
},
},
});
<Button $variant="primary">Click</Button>;The lint/format plugins (@styled-cva/eslint-plugin, @styled-cva/prettier-plugin, @styled-cva/biome-plugin) key off the tw identifier in source code, not the runtime. They work identically under Preact.
Caveats
- No dedicated
@styled-cva/preactpackage. The alias is the supported path. If you need to avoid bundler configuration, file an issue with the use case. forwardRefdeprecation in React 19 does not affect Preact users —preact/compatstill exports a workingforwardRef.- JSX namespace must resolve to React types under the alias.
preact/compatre-exports them, so polymorphic$asprops and intrinsic element typings keep working.