Documentation
Guides
Adapting based on props

Adapting based on props

You can pass a function to twc to adapt the classes based on props.

Usage

This button component has a primary state that changes its color. When setting the $primary prop to true, we are changing its style.

import { twc, TwcComponentProps } from "react-twc";
 
type ButtonProps = TwcComponentProps<"button"> & { $primary?: boolean };
 
const Button = twc.button<ButtonProps>((props) => [
  "font-semibold border border-blue-500 rounded",
  props.$primary ? "bg-blue-500 text-white" : "bg-white text-gray-800",
]);
 
export default () => (
  <div>
    <Button>Normal</Button>
    <Button $primary>Primary</Button>
  </div>,
);

You may have noticed the usage of TwcComponentProps<"button">, it is a helper to return props accepted by a twc component. It's similar to React.ComponentProps<"button"> with asChild prop and className type from clsx.

Why is the prop prefixed by a dollar?

We call the prop $primary a "transient prop". A transient prop starts with a $, it can be consumed by the uppermost component layer but are not passed to the underlying components. It our case, it means the <button> will not get a <button $primary="true"> attribute in the DOM.

Use with cva

For complex use cases, you can use cva (opens in a new tab) with twc to have more control on the variants.

import { twc, TwcComponentProps } from "react-twc";
import { cva } from "class-variance-authority";
 
const button = cva("font-semibold border border-blue-500 rounded", {
  variants: {
    $intent: {
      primary: "bg-blue-500 text-white",
      secondary: "bg-white text-gray-800",
    },
  },
  defaultVariants: {
    $intent: "primary",
  },
});
 
type ButtonProps = TwcComponentProps<"button"> & VariantProps<typeof button>;
 
const Button = twc.button<ButtonProps>(({ $intent }) => button({ $intent }));
 
export default () => (
  <div>
    <Button>Primary button (default)</Button>
    <Button $intent="primary">Primary button</Button>
    <Button $intent="secondary">Secondary button</Button>
  </div>
);

Customize transient props

By default, all props starting with a $ are considered transient. This is a is a hint that it is meant exclusively for the uppermost component layer and should not be passed further down. In other terms, it prevents your DOM element to have unexpected props.

If you don't like the $ prefix, you can customize transient props for a specific component using transientProps constructor.

import { twc, TwcComponentProps } from "react-twc";
 
type ButtonProps = TwcComponentProps<"button"> & { primary?: boolean };
 
// The "primary" prop is marked as transient
const Button = twc.button.transientProps(["primary"])<ButtonProps>((props) => [
  "font-semibold border border-blue-500 rounded",
  props.primary ? "bg-blue-500 text-white" : "bg-white text-gray-800",
]);
 
export default () => (
  <div>
    <Button>Normal</Button>
    {/* The "primary" attribute will not be forwarded to the <button> element. */}
    <Button primary>Primary</Button>
  </div>,
);

transientProps also accepts a function:

const Button = twc.button.transientProps(
  (prop) => prop === "primary",
)<ButtonProps>((props) => [
  "font-semibold border border-blue-500 rounded",
  props.primary ? "bg-blue-500 text-white" : "bg-white text-gray-800",
]);

It is also possible to configure this behaviour globally by creating a custom instance of twc:

import { clsx } from "clsx";
import { createTwc } from "react-twc";
 
export const twx = createTwc({
  // Forward all props not starting by "_"
  shouldForwardProp: (prop) => prop[0] !== "_",
});