Get started / Basics

Base Component

The following examples show how to create a base component and how to extend it with custom properties.

Create a base component

Select the component tag you wish by using it's intrinsic tag name. For example rc.div or rc.button. Via the interpolation you are able to pass classnames.

export const CustomButton = rc.button`
  text-blue
  px-3
  py-2
`

Implementation

<CustomButton>Hello World</CustomButton>

// renders:
// <button class="text-blue px-3 py-2">Hello World</button>

Custom Properties

Important: Prefix rc-specific properties with $ to not pass them to the created/extended component and to avoid conflicts with intrinsic properties (rc filters out props prefixed with $)
interface CustomButtonProps {
  $customState?: boolean
}

const CustomButton = rc.button<CustomButtonProps>`
  text-blue
  px-3
  py-2
  ${(p) => (p.$customState ? "bg-black" : "")}
`

Implementation

<CustomButton $customState>Button</CustomButton>

// renders:
// <button class="text-blue px-3 py-2 bg-black">Button</button>

Intrinsic Properties

rc is passing intrinsic properties and you can use them in the interpolation string. For typescript we provide the type of IntrinsicElements to get them properly validated.

interface ButtonProps extends JSX.IntrinsicElements["button"] {
  $customState?: "button"
}

const CustomButton = rc.button<ButtonProps>`
  text-blue 
  custom
  ${(p) => (p.$customState" ? "bg-black" : "")}
  ${(p) => (p.type === "button" ? "opacity-60" : "")}
`

Implementation

<CustomButton type="button" $customState>Button</CustomButton>

// renders:
// <button type="button" class="text-blue custom opacity-60 bg-black"></button>

Declare rc components inside React components

Need to define a component during render? Wrap the factory in useClassmate to memoize it between renders and avoid re-instantiating the element tree. Pass dependencies just like you would to React.useMemo if the factory relies on runtime values.

import rc, { useClassmate } from "react-classmate"

type WorkoutStatus = "completed" | "pending"

const WorkoutDay = ({ status }: { status: WorkoutStatus }) => {
  const StyledDay = useClassmate(
    () =>
      rc.div.variants<{ $status: WorkoutStatus }>({
        base: "rounded-md border p-4 text-sm",
        variants: {
          $status: {
            completed: "border-green-400 bg-green-50",
            pending: "border-yellow-400 bg-yellow-50",
          },
        },
      }),
    [status],
  )

  return <StyledDay $status={status}>Workout details</StyledDay>
}

Colocate logic with your component

Use .logic() before calling your template literal to run arbitrary JavaScript for every render. Whatever object you return is merged back into the props so you can derive variant values, data attributes, or additional $ props without wiring extra hooks.

import rc from "react-classmate"

type DayStatus = "completed" | "pending"

interface WorkoutProps {
  workouts: unknown[]
  allResolved: boolean
  hasCompleted: boolean
  hasSkipped: boolean
  $status?: DayStatus
}

const deriveDayStatus = ({ workouts, allResolved, hasCompleted, hasSkipped }: WorkoutProps): DayStatus => {
  if (workouts.length === 0) return "pending"
  if (allResolved && hasCompleted && !hasSkipped) return "completed"
  return "pending"
}

export const WorkoutDay = rc.div
  .logic<WorkoutProps>((props) => {
    const status = deriveDayStatus(props)
    return {
      $status: status,
      ["data-status"]: status,
    }
  })
  .variants<WorkoutProps, { $status: DayStatus }>({
    base: "rounded-md border p-4 text-sm transition-colors",
    variants: {
      $status: {
        completed: "border-green-500 bg-green-50",
        pending: "border-slate-300 bg-white",
      },
    },
  })