Skip to Content
styled-cva 0.7.1 is available 🎉
Getting Started

Getting Started

This guide will help you get started with styled-cva.

Installation

Install styled-cva using your preferred package manager:

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

bash npm i --save preact @styled-cva/react

Bundler alias

// 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/preact package. The alias is the supported path. If you need to avoid bundler configuration, file an issue with the use case.
  • forwardRef deprecation in React 19 does not affect Preact users — preact/compat still exports a working forwardRef.
  • JSX namespace must resolve to React types under the alias. preact/compat re-exports them, so polymorphic $as props and intrinsic element typings keep working.
Last updated on