Getting Started
App Providerglobal.d.tsThemesTypes
cn(...args)Text ParserUnits Converters
AnchorAvatarBreadcrumbBurgerButtonCardCarouselCheckerCodeColor PickerCommandConfettiCopyButtonDouble Helix WordsFloating IndicatorGroupHighlight TextIndicatorInputKbdLabelLoaderPaginationPassword RequirementPolymorphic SlotProgressProseRatingRunning AreaScroll AreaSheetsSkeletonSliderStackSvgTableTabsTextareaTimelineTimesToasterTooltipTyping WordsTypography
useClickOutsideuseClipboarduseDeviceInfouseDialoguseDidUpdateuseDirectionuseDisclosureuseDocumentTitleuseDocumentVisibilityuseElementInfouseEyeDropperuseFetchuseFullscreenuseGeoLocationuseHotkeysuseHoveruseIduseImagePopupuseInputStateuseIntersectionuseIntervaluseIsomorphicEffectuseListStateuseLocalStorageuseMeasureScrollbaruseMediaQueryuseMergedRefuseMouseuseMoveuseMutationObserveruseNetworkuseOpenStateuseOrientationuseOSusePaginationusePWAInstalleruseRandomColorsuseReducedMotionuseReloaduseResizeObserveruseScrollIntoViewuseStateHistoryuseTimeoutuseTouchuseTriggeruseUncontrolleduseValidatedStateuseViewportSizeuseWindowEventuseWindowScroll
Docs
Web
Components
Checker
Carousel

A swipeable or clickable slider that showcases a collection of items in an interactive manner.

Code

A code display component that enhances the readability of code snippets with optional syntax highlighting.


Edit this page on GitHub
  • Started
  • Utilities
  • Configuration
  • Components
  • Hooks
  • Examples
  • Github
  • Contributing
⌘+J

© 2025 oeri rights MIT


Designed in Earth-616

Built by oeri

Checker

A combined component for checkbox, switch, and radio button functionality. Useful for managing multiple selections or binary choices.

Usage

Basic usage example to understand how the Checker component functions. Demonstrates the checkbox, switch, and radio options with default behavior and interaction.

"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";
import { Group } from "@/ui/group";

export function CheckerDemo() {
  const [checked, setChecked] = useState(false);
  return (
    <Group>
      <Checker label="Switch" />
      <Checker type="checkbox" label="Checkbox" />
      <Checker type="radio" label="Radio" checked={checked} onClick={() => setChecked(c => !c)} />
    </Group>
  );
}

Properties

Interactive configurator for exploring different customization options in the Checker component. Customize icon styles, thumb behavior, and state transitions (e.g., checked, unchecked, indeterminate).

Size
Color
"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";

export function CheckerDemo() {
  const [checked, setChecked] = useState(false);
  return <Checker label="I agree to sell my privacy" onLabel="ON" offLabel="OFF" defaultChecked checked={checked} onCheckedChange={event => setChecked(event.currentTarget.checked)} />;
}

Change Icon

Explore how to modify the appearance of the icons used in the Checker. This section allows you to replace the default icons with custom options, providing a more personalized look.

Change Icon Labels

Demonstrates how to customize the labels associated with the icons in the Checker. Helpful when you want to adjust the appearance or add specific text annotations to your checkboxes, switches, or radios.

"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";
import { MoonStarIcon, SunIcon } from "@/icons/*";

export function CheckerChangeIconLabels() {
  return (
    <Checker
      type="switch"
      color="#543a7b"
      label="Icon Labels"
      onLabel={<SunIcon size={16} stroke={2} color="#fff" />}
      offLabel={<MoonStarIcon size={16} stroke={2} color="rgb(34, 139, 230)" />}
      className="cursor-pointer rounded-xl border px-3 py-2.5 font-medium transition-colors [&:has(input:checked)]:bg-[#6e5494]"
    />
  );
}

Change Thumb Icon

Shows how to customize the thumb icon in switches. Allows for greater flexibility in design by offering the ability to replace the default thumb with your custom design.

"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";
import { CheckIcon, XIcon } from "@/icons/*";

export function CheckerChangeThumbIcon() {
  const [checked, setChecked] = useState(false);
  return (
    <Checker
      type="switch"
      size={36}
      color="#930464"
      label="Icon Labels"
      checked={checked}
      onCheckedChange={event => setChecked(event.currentTarget.checked)}
      onLabel="ON"
      offLabel="OFF"
      icon={checked ? <CheckIcon animation size={22} stroke={3} /> : <XIcon animation size={22} stroke={3} />}
      classNames={{ track: "[&_svg]:text-red-500 [&>svg]:data-[checkbox]:data-[checked]:text-yellow-500 [&>svg]:data-[radio]:data-[checked]:text-color", thumb: "[&>svg]:data-[switch]:data-[checked]:text-red-900" }}
    />
  );
}

Checker.Group

Introduces the Checker.Group, which groups multiple Checker components together for easier handling and consistent state management. Useful for managing groups of checkboxes or radio buttons where mutual states need to be controlled.

Select your favorite framework/library

This is anonymous

Size
Color
"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";
import { Group } from "@/ui/group";

const data = [
  { value: "react", label: "React" },
  { value: "svelte", label: "Svelte" },
  { value: "ng", label: "Angular" },
  { value: "vue", label: "Vue" }
];

export function CheckerGroupDemo() {
  const [value, setValue] = useState<string[] | string | null>(["react", "ng"]);
  return (
    <Checker.Group label="Select your favorite framework/library" description="This is anonymous" value={value} onValueChange={setValue}>
      <Group>
        {data.map(i => (
          <Checker key={i.value} id={i.value} {...i} />
        ))}
      </Group>
    </Checker.Group>
  );
}

Group.Card

Demonstrates the use of Checker.Group within a card-like UI structure for better organization and presentation. Ideal for grouping related items in a visually appealing way.

"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";
import { Group } from "@/ui/group";

const data = [
  { value: "react", label: "React" },
  { value: "svelte", label: "Svelte" },
  { value: "ng", label: "Angular" },
  { value: "vue", label: "Vue" }
];

export function CheckerGroupCardDemo() {
  const [value, setValue] = useState<string[] | string | null>(["react", "ng"]);
  return (
    <Checker.Group value={value} onValueChange={setValue} classNames={{ group: "grid grid-cols-2 gap-2" }}>
      {data2.map(i => (
        <Checker.Card key={i.value} value={i.value}>
          <Checker id={i.value} {...i} />
        </Checker.Card>
      ))}
    </Checker.Group>
  );
}

Current Selected

Displays how the Checker component handles single or multiple selected items within a group. Ideal for showcasing the active state or the currently selected checkbox, switch, or radio.

CurrentValue: –

"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";
import { Group } from "@/ui/group";
import { Stack } from "@/ui/stack";
import { Typography } from "@/ui/typography";

const data = [
  { value: "react", label: "React", description: "A JavaScript library for building user interfaces." },
  { value: "svelte", label: "Svelte", description: "A compiler that generates minimal and efficient JavaScript code." },
  { value: "ng", label: "Angular", description: "A platform for building mobile and desktop web applications." },
  { value: "vue", label: "Vue", description: "A progressive JavaScript framework for building UI.", error: "Compatibility issues with older versions of Internet Explorer." },
];

export function CheckerCurrentSelectedDemo() {
  const [value, setValue] = useState<string | string[] | null>(null);

  const cards = data.map(i => (
    <Checker.Card key={i.value} value={i.value}>
      <Checker id={i.value} {...i} />
    </Checker.Card>
  ));

  return (
    <Stack>
      <Checker.Group value={value} onValueChange={setValue}>
        <Group>{cards}</Group>
      </Checker.Group>

      <Typography prose="p" className="mt-8">
        CurrentValue: {value || "–"}
      </Typography>
    </Stack>
  );
}

Multiple Selected

Shows how to handle multiple selected items in the Checker.Group. Useful for scenarios where multiple choices can be selected simultaneously, such as multi-checkbox forms.

CurrentValue: -

"use client";
import { useState } from "react";
import { Checker } from "@/ui/checker";
import { CheckIcon, XIcon } from "@/icons/*";

const data = [
  { value: "next", label: "Next.js", description: "A React framework for building server-rendered applications with great performance and scalability." },
  { value: "gatsby", label: "Gatsby", description: "A React-based framework optimized for building fast, static websites with GraphQL.", error: "Potential performance issues with large data sources due to build times." },
  { value: "remix", label: "Remix", description: "A full-stack framework for building modern web applications focused on user experience and performance.", error: "Limited ecosystem compared to Next.js and Gatsby." },
];

export function CheckerMultipleSelectedDemo() {
  const [value, setValue] = useState<string[] | string | null>([]);

  return (
    <>
      <Checker.Group multiple value={value} onValueChange={setValue} classNames={{ group: "gap-3" }}>
        {data.map(i => (
          <Checker.Card key={i.value} value={i.value}>
            <Checker
              {...i}
              id={i.value}
              icon={value?.includes(i.value) ? <CheckIcon size={12} stroke={3} color="green" /> : <XIcon size={12} stroke={3} color="red" />}
            />
          </Checker.Card>
        ))}
      </Checker.Group>
      <p className="mt-8 w-full text-left text-sm text-muted-foreground">CurrentValue: {value?.length ? JSON.stringify(value) : "-"}</p>
    </>
  );
}

Indeterminate State

Demonstrates how to implement and visually represent the indeterminate state in checkboxes. The indeterminate state is useful for scenarios like "select all" where some but not all items are checked.

"use client";
import { Checker } from "@/ui/checker";
import { useListState } from "@/hooks/use-list-state";

const initialValues = [
  { label: "Receive email notifications", checked: false },
  { label: "Receive sms notifications", checked: false },
  { label: "Receive push notifications", checked: false }
];

export function CheckerIndeterminateStateDemo() {
  const [values, handlers] = useListState(initialValues);

  const allChecked = values.every(value => value.checked);
  const indeterminate = values.some(value => value.checked) && !allChecked;

  const items = values.map((value, index) => (
    <Checker
      type="checkbox"
      key={index}
      label={value.label}
      className="ml-8 mt-2"
      checked={value.checked}
      onCheckedChange={event => handlers.setItemProp(index, "checked", event.currentTarget.checked)}
    />
  ));

  return (
    <div className="m-auto grid grid-flow-row">
      <Checker
        type="checkbox"
        checked={allChecked}
        indeterminate={indeterminate}
        label="Receive all notifications"
        onCheckedChange={() => handlers.setState(current => current.map(value => ({ ...value, checked: !allChecked })))}
      />
      {items}
    </div>
  );
}

API References

Styles API

Styles APITypeDefaultAnnotation
unstyled?Partial<Record<T, boolean>>falseif true, all default styles will be removed
classNames?Partial<Record<T, string>>undefinedSet className for T
styles?Partial<Record<T, CSSProperties>>undefinedSet style for T

Props API

Checker

type IconType = ReactNode | ((checked: boolean) => React.ReactNode);
Checker Props APITypeDefaultAnnotation
defaultChecked?boolean
checked?boolean
onCheckedChange?ChangeEventHandler<HTMLInputElement>undefined
id?stringId used to bind input and label, if not passed, unique id will be generated instead
label?React.ReactNodeContent of the label associated with the radio
icon?IconTypeIcon displayed when checked is in checked or indeterminate state
offLabel?React.ReactNodeInner label when the Checker at type="switch" is in unchecked state
onLabel?React.ReactNodeInner label when the Checker at type="switch" is in checked state
color?Property.ColorKey of CSS color to set input color in checked state.
size?string | numberControls size of all elements
round?string | number9999Key of CSS value to set `border-radius
wrapperProps?ComponentProps<"div">Props passed down to the root element
labelPosition?"left" | "right"rightPosition of the label relative to the input
description?React.ReactNodeundefinedDescription displayed below the label
error?React.ReactNodeundefinedError displayed below the label
rootRef?ForwardedRef<HTMLDivElement>Assigns ref of the root element
indeterminate?booleanfalseIndeterminate state of the checkbox. If set, checked prop at type="checkbox" is ignored.

CheckerGroup

type CheckerValue = string | string[] | null;
CheckerGroup Props APITypeDefaultAnnotation
children?React.ReactNodeRequired Checker components
value?CheckerValueundefinedControlled component value
defaultValue?CheckerValueundefinedDefault value for uncontrolled component
onValueChange?(value: CheckerValue) => voidundefinedCalled when value changes
wrapperProps?ComponentProps<"div">undefinedProps passed down to the wrapper
size?(string & {}) | number20Controls size of the wrapper
readOnly?booleanfalseIf set, value cannot be changed
indeterminate?booleanfalseIndeterminate state of the checked. If set, checked prop at type="checkbox" is ignored.

Depend on

  • useId
  • useUncontrolled

Source Codes

Full working code example, including necessary markup and styles. You can copy and paste this code directly to start using the component immediately.

checker.tsx
globals.css
/* checker */ @layer base { .stylelayer-checkergroup { & [data-checkergroup="card"] { @apply transition-[border-color] rounded-[--checker-card-round] border data-[checked=true]:border-constructive data-[label-position=left]:justify-end data-[label-position=right]:justify-start focus-visible:ring-0 focus-visible:border-constructive focus-visible:outline-constructive focus-visible:outline-offset-2 focus-visible:outline focus-visible:outline-2 [&_*]:pointer-events-none [-webkit-tap-highlight-color:transparent]; &:has([data-switch="error"], [data-checkbox="error"], [data-radio="error"]) { @apply border-destructive focus-visible:outline-destructive; } & [data-switch="root"], & [data-checkbox="root"], & [data-radio="root"] { @apply w-full; &[data-label-position="left"] { & [data-switch="labelWrapper"], & [data-checkbox="labelWrapper"], & [data-radio="labelWrapper"] { @apply mr-auto; } } } } } .stylelayer-switch, .stylelayer-checkbox, .stylelayer-radio { &, & *, & * * { @apply text-left [-webkit-tap-highlight-color:transparent]; } } .stylelayer-switch { &:not(:has([data-switch="description"], [data-switch="error"])) { @apply items-center; } &[data-label-position="left"] { & [data-switch="track"] { @apply order-2; } & [data-switch="labelWrapper"] { @apply order-1; } } & input:focus-visible { & + [data-switch="track"] { @apply outline outline-2 outline-offset-2 outline-[--switch-bg,hsl(var(--constructive))]; } } &:has(input:disabled, input:where([data-disabled])) { @apply [--switch-bg:hsl(var(--muted)/0.5)] [--switch-bd:unset] [--switch-thumb-bg:hsl(var(--muted-foreground))] [--switch-thumb-bd:--switch-thumb-bg] [--switch-track-label-color:--switch-thumb-bg] [--switch-cursor:not-allowed]; } &:has(input:not(:checked)) { &[data-error] { @apply [--switch-bd:hsl(var(--destructive))]; } } &:has(input:checked) { @apply [--switch-bg:--switch-color] [--switch-bd:--switch-color] [--switch-thumb-transform:--switch-thumb-sz] [--switch-thumb-bg:white] [--switch-thumb-bd:--switch-thumb-bg] [--switch-transform:calc(var(--switch-thumb-transform,0px)+var(--switch-thumb-margin))]; & [data-switch="track"] { &[data-disabled] { @apply opacity-50 cursor-not-allowed pointer-events-none; } } & [data-switch="trackLabel"] { @apply ml-0 mr-[calc(var(--switch-thumb-sz)+var(--switch-thumb-margin))]; } } & [data-switch="track"] { @apply relative overflow-hidden transition-colors appearance-none flex items-center place-content-center select-none z-[0] cursor-[--switch-cursor,pointer] rounded-[--switch-round] bg-[--switch-bg,hsl(var(--muted))] [border:0.0625rem_solid_var(--switch-bd,hsl(var(--border)))] h-[--switch-h] min-h-[--switch-h] max-h-[--switch-h] w-[--switch-w] min-w-[--switch-w] max-w-[--switch-w] m-0 leading-[0]; } & [data-switch="thumb"] { @apply absolute z-[1] flex rounded-[--switch-round] bg-[--switch-thumb-bg,hsl(var(--color))] size-[--switch-thumb-sz] [border:0.0625rem_solid_var(--switch-thumb-bd)] left-0 inset-y-1/2 transition-transform [&>*]:m-auto [transform:translate(calc(var(--switch-transform,0%)+12%),-50%)]; } & [data-switch="trackLabel"] { @apply h-full grid place-content-center min-w-[calc((var(--switch-w)-var(--switch-thumb-sz))-((var(--switch-h)-0.125rem)*(15/100)))] px-[--switch-thumb-padding] ml-[calc(var(--switch-thumb-sz)+var(--switch-thumb-margin))] transition-[margin] [font-size:--switch-label-fz] font-semibold text-[--switch-track-label-color,hsl(var(--color))]; } } .stylelayer-checkbox { &:not(:has([data-checkbox="description"], [data-checkbox="error"])) { @apply items-center; } &[data-label-position="left"] { & [data-checkbox="track"] { @apply order-2; } & [data-checkbox="labelWrapper"] { @apply order-1; } } & input:focus-visible { & + [data-checkbox="track"] { @apply outline outline-2 outline-offset-2 outline-[--switch-bg,hsl(var(--constructive))]; } } &:has(input:disabled, input:where([data-disabled])) { @apply [--checkbox-bg:hsl(var(--muted)/0.5)] [--checkbox-bd:unset] [--checkbox-cursor:not-allowed]; } &:has(input:not(:checked)) { &[data-error] { @apply [--checkbox-bd:hsl(var(--destructive))]; } } &:has(input[data-indeterminate]), &:has(input:checked) { @apply [--checkbox-bg:--checkbox-color] [--checkbox-bd:--checkbox-color]; & [data-checkbox="track"] { &[data-disabled] { @apply opacity-50 cursor-not-allowed pointer-events-none; } & > svg { @apply opacity-100 transform-none; } } } & [data-checkbox="track"] { @apply relative overflow-hidden transition-colors appearance-none flex items-center place-content-center select-none z-[0] cursor-[--checkbox-cursor,pointer] rounded-[--checkbox-round] bg-[--checkbox-bg,hsl(var(--muted))] [border:0.0625rem_solid_var(--checkbox-bd,hsl(var(--border)))] h-[--checkbox-sz] min-h-[--checkbox-sz] max-h-[--checkbox-sz] w-[--checkbox-sz] min-w-[--checkbox-sz] max-w-[--checkbox-sz] m-0 leading-[0]; & > svg { @apply absolute inset-0 m-auto pointer-events-none size-[--checkbox-icon-sz,60%] [transition:transform_.1s_ease-in,opacity_.1s_ease-in]; &[data-unchecked="hidden"] { @apply opacity-0 [transform:translateY(calc(.3125rem))_scale(.5)]; } } } } .stylelayer-radio { &:not(:has([data-radio="description"], [data-radio="error"])) { @apply items-center; } &[data-label-position="left"] { & [data-radio="track"] { @apply order-2; } & [data-radio="labelWrapper"] { @apply order-1; } } & input:focus-visible { & + [data-radio="track"] { @apply outline outline-2 outline-offset-2 outline-[--switch-bg,hsl(var(--constructive))]; } } &:has(input:disabled, input:where([data-disabled])) { @apply [--radio-bg:hsl(var(--muted)/0.5)] [--radio-bd:unset] [--radio-thumb-bg:hsl(var(--muted-foreground))] [--radio-thumb-bd:--radio-thumb-bg] [--radio-track-label-color:--radio-thumb-bg] [--radio-cursor:not-allowed]; } &:has(input:not(:checked)) { &[data-error] { @apply [--radio-bd:hsl(var(--destructive))]; } } &:has(input:checked) { @apply [--radio-bg:--radio-color] [--radio-bd:--radio-color] [--radio-thumb-transform:--radio-thumb-sz] [--radio-thumb-bg:white] [--radio-thumb-bd:--radio-thumb-bg] [--radio-transform:calc(var(--radio-thumb-transform,0px)+var(--radio-thumb-margin))]; & [data-radio="track"] { &[data-disabled] { @apply opacity-50 cursor-not-allowed pointer-events-none; } & > svg { @apply opacity-100 transform-none; } } } & [data-radio="track"] { @apply relative overflow-hidden transition-colors appearance-none flex items-center place-content-center select-none z-[0] cursor-[--radio-cursor,pointer] rounded-[--radio-round] bg-[--radio-bg,hsl(var(--muted))] [border:0.0625rem_solid_var(--radio-bd,hsl(var(--border)))] h-[--radio-sz] min-h-[--radio-sz] max-h-[--radio-sz] w-[--radio-sz] min-w-[--radio-sz] max-w-[--radio-sz] m-0 leading-[0]; & > svg { @apply absolute inset-0 m-auto pointer-events-none size-[--radio-icon-sz,60%] [transition:transform_.1s_ease-in,opacity_.1s_ease-in]; &[data-unchecked="hidden"] { @apply opacity-0 [transform:translateY(calc(.3125rem))_scale(.5)]; } } } & [data-radio="thumb"] { @apply absolute z-[1] flex rounded-[--radio-round] bg-[--radio-thumb-bg,hsl(var(--color))] size-[--radio-thumb-sz] [border:0.0625rem_solid_var(--radio-thumb-bd)] left-0 inset-y-1/2 transition-transform [&>*]:m-auto [transform:translate(calc(var(--radio-transform,0%)+12%),-50%)]; } } }