Skip to Content
styled-cva logostyled-cva

Getting Started

How to install and start using styled-cva in your project.

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

Installation

Install styled-cva using your preferred package manager:

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 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.

On this page