---
title: "Tools"
description: "Read a component's variant names, values, and defaults with getSchema from cva/tools."
url: "https://cva.style/beta/getting-started/tools/"
---

The `cva/tools` entry point holds the helpers that read a component’s variants back out at runtime. It’s a separate entry point, so an ESM bundler drops it from a build that never imports it.

## `getSchema`

Re-declaring variants for a Storybook story or prop table creates a second copy to keep in sync by hand. `getSchema` reads them from the component instead, so you declare each variant once:

```ts
import { cva } from "cva";
import { getSchema } from "cva/tools";


const button = cva({
  base: "button",
  variants: {
    intent: { primary: "button--primary", secondary: "button--secondary" },
    size: { small: "button--small", large: "button--large" },
  },
  defaultVariants: { intent: "primary", size: "small" },
});


getSchema(button);
// => {
//   intent: { values: ["primary", "secondary"], defaultValue: "primary" },
//   size: { values: ["small", "large"], defaultValue: "small" },
// }
```

The schema is fully typed: `values` narrows to the variant’s literal values, and `defaultValue` only appears when the component declares one. Hover the result in your editor and you see the same shape the runtime returns:

```ts
import { cva } from "cva";
import { getSchema } from "cva/tools";


const badge = cva({
  base: "badge",
  variants: {
    tone: { info: "badge--info", warning: "badge--warning" },
    round: { true: "badge--round", false: "badge--square" },
    weight: { 400: "badge--regular", 700: "badge--bold" },
  },
  defaultVariants: { tone: "info" },
});


const schema = getSchema(badge);
//    ^ {
//        tone: {
//          values: readonly ("info" | "warning")[];
//          defaultValue: "info";
//        };
//        round: { values: readonly boolean[] };
//        weight: { values: readonly (400 | 700)[] };
//      }
```

Boolean and numeric variant keys come back as booleans and numbers, matching the props the component accepts rather than the object keys they were written as. [Internal variants](https://cva.style/beta/getting-started/variants#internal-variants) are omitted.

For the full signature, see the [API reference](https://cva.style/beta/api-reference#getschema).

## Generate a React variant gallery

The runnable [React with Tailwind CSS example](https://cva.style/beta/examples/react/tailwindcss#basic-component) exports its `button` class function separately from its React wrapper. The wrapper supplies `intent`, `size`, and `disabled` defaults so styling and native attributes use the same values:

examples/beta/react-with-tailwindcss/src/components/button/button.tsx

```tsx
import React from "react";
import { cva, type VariantProps } from "cva";


export const button = cva({
  base: "font-semibold border rounded",
  variants: {
    intent: {
      primary: "bg-blue-500 text-white border-transparent",
      secondary: "bg-white text-gray-800 border-gray-400",
    },
    size: {
      small: "py-1 px-2 text-sm",
      medium: "py-2 px-4 text-base",
    },
    disabled: {
      false: null,
      true: "opacity-50 cursor-not-allowed",
    },
  },
  compoundVariants: [
    {
      intent: "primary",
      disabled: false,
      class: "hover:bg-blue-600",
    },
    {
      intent: "secondary",
      disabled: false,
      class: "hover:bg-gray-100",
    },
    { intent: "primary", size: "medium", class: "uppercase" },
  ],
});


export interface ButtonProps
  extends
    Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "disabled">,
    VariantProps<typeof button> {}


export const Button: React.FC<ButtonProps> = ({
  className,
  intent = "primary",
  size = "medium",
  disabled = false,
  ...props
}) => (
  <button
    className={button({ intent, size, disabled, className })}
    disabled={disabled}
    {...props}
  />
);
```

Pass `button`, the `cva` class function, to `getSchema`. The React `Button` renders markup and is not a valid input. Read the schema outside the render function, then map its typed values into buttons:

examples/beta/react-with-tailwindcss/src/button-gallery.tsx

```tsx
import React from "react";
import { cx } from "cva";
import { getSchema } from "cva/tools";
import { Button, button } from "./components";


const schema = getSchema(button);
const intents = [undefined, ...schema.intent.values];
const sizes = [undefined, ...schema.size.values];
const isDisabled = schema.disabled.values;


export function ButtonGallery() {
  return (
    <table
      className={cx(
        "relative h-max w-max self-center justify-self-center",
        "[&_:where(th,td)]:p-2",
      )}
    >
      <caption>Button variants</caption>
      <thead>
        <tr>
          <td></td>
          <td></td>
          {intents.map((intent) => (
            <th key={intent || "default"} scope="col">
              {intent || "default"}
            </th>
          ))}
        </tr>
      </thead>
      {isDisabled.map((disabled) => (
        <tbody key={String(disabled)}>
          {sizes.map((size, index) => (
            <tr key={`${disabled}-${size || "default"}`}>
              {index === 0 && (
                <th scope="rowgroup" rowSpan={sizes.length}>
                  {disabled ? "disabled" : "enabled"}
                </th>
              )}
              <th scope="row">{size || "default"}</th>
              {intents.map((intent) => (
                <td key={intent || "default"}>
                  <Button intent={intent} size={size} disabled={disabled}>
                    {intent || "default"} button
                  </Button>
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      ))}
    </table>
  );
}
```

Adding an intent or size to `button` now adds it to the gallery without another list to update. The default cells leave those props unset, exercising the React wrapper’s prop defaults, while each row passes `disabled` explicitly. `getSchema` cannot discover wrapper defaults, so use `defaultVariants` when the class function itself should expose a `defaultValue`.

The [React examples](https://cva.style/beta/examples/react/tailwindcss) use this approach with default, size, intent, and disabled states.

Keep schema inspection in stories

Read a schema outside rendering and derive layout counts from its `values` arrays. Keep this inspection in stories or documentation when the application does not need it at runtime.
