Dice UI
Utilities

Hitbox

A utility component that extends the clickable area of child elements for improved accessibility and user experience.

API
import { Checkbox } from "@/components/ui/checkbox";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxDemo() {
  return (
    <div className="flex items-center gap-8">
      <div className="flex flex-col items-center gap-4">
        <Hitbox debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">Default Size</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox radius="full" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">Full Radius</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox position="bottom" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">Bottom Position</p>
      </div>
    </div>
  );
}

Installation

CLI

npx shadcn@latest add "https://diceui.com/r/hitbox"

Manual

Install the following dependencies:

npm install @radix-ui/react-slot class-variance-authority

Copy and paste the following code into your project.

import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
 
import { cn } from "@/lib/utils";
 
type Size = "default" | "sm" | "lg";
type DynamicSize = Size | (string & {});
 
const sizes: DynamicSize[] = ["default", "sm", "lg"];
 
const hitboxVariants = cva(
  "relative [--size-default:12px] [--size-lg:16px] [--size-sm:8px] after:absolute after:content-['']",
  {
    variants: {
      size: {
        default: "[--size:var(--size-default)]",
        sm: "[--size:var(--size-sm)]",
        lg: "[--size:var(--size-lg)]",
        dynamic: "[--size:var(--size)]",
      },
      position: {
        all: "after:[inset:calc(-1*var(--size))]",
        top: "after:[height:var(--size)] after:[left:0] after:[right:0] after:[top:calc(-1*var(--size))]",
        bottom:
          "after:[bottom:calc(-1*var(--size))] after:[height:var(--size)] after:[left:0] after:[right:0]",
        left: "after:[bottom:0] after:[left:calc(-1*var(--size))] after:[top:0] after:[width:var(--size)]",
        right:
          "after:[bottom:0] after:[right:calc(-1*var(--size))] after:[top:0] after:[width:var(--size)]",
        vertical:
          "after:[bottom:calc(-1*var(--size))] after:[left:0] after:[right:0] after:[top:calc(-1*var(--size))]",
        horizontal:
          "after:[bottom:0] after:[left:calc(-1*var(--size))] after:[right:calc(-1*var(--size))] after:[top:0]",
      },
      radius: {
        none: "",
        sm: "after:rounded-sm",
        md: "after:rounded-md",
        lg: "after:rounded-lg",
        full: "after:rounded-full",
      },
      debug: {
        true: "after:border after:border-red-500 after:border-dashed after:bg-red-500/20",
        false: "",
      },
    },
    defaultVariants: {
      size: "default",
      position: "all",
      radius: "none",
      debug: false,
    },
  },
);
 
interface HitboxProps
  extends React.ComponentProps<typeof Slot>,
    Omit<VariantProps<typeof hitboxVariants>, "size"> {
  size?: DynamicSize;
}
 
function Hitbox(props: HitboxProps) {
  const {
    className,
    style,
    size,
    position,
    radius,
    debug = false,
    ...hitboxProps
  } = props;
 
  const isDynamicSize = size && !sizes.includes(size);
 
  return (
    <Slot
      {...hitboxProps}
      className={cn(
        hitboxVariants({
          size: isDynamicSize ? "dynamic" : (size as Size),
          position,
          radius,
          debug,
        }),
        className,
      )}
      style={{
        ...style,
        ...(isDynamicSize && { "--size": size }),
      }}
    />
  );
}
 
export { Hitbox };

Layout

Import the component and wrap it around any element to extend its clickable area.

import { Hitbox } from "@/components/hitbox";

<Hitbox>
  <Button>Click me</Button>
</Hitbox>

Examples

Sizes

Control the size of the extended hitbox area.

import { Checkbox } from "@/components/ui/checkbox";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxSizesDemo() {
  return (
    <div className="flex items-center gap-8">
      <div className="flex flex-col items-center gap-4">
        <Hitbox debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">default</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox size="sm" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">sm</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox size="lg" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">lg</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox size="10px" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">10px</p>
      </div>
    </div>
  );
}

Positions

Control which sides of the element the hitbox extends to.

import { Checkbox } from "@/components/ui/checkbox";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxPositionsDemo() {
  return (
    <div className="grid grid-cols-2 gap-8 md:grid-cols-4">
      <div className="flex flex-col items-center gap-4">
        <Hitbox debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">all</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox position="top" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">top</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox position="bottom" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">bottom</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox position="left" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">left</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox position="right" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">right</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox position="vertical" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">vertical</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox position="horizontal" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">horizontal</p>
      </div>
    </div>
  );
}

Radii

Control the border radius of the hitbox area.

import { Checkbox } from "@/components/ui/checkbox";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxRadiiDemo() {
  return (
    <div className="flex items-center gap-8">
      <div className="flex flex-col items-center gap-4">
        <Hitbox radius="none" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">none</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox radius="sm" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">sm</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox radius="md" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">md</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox radius="lg" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">lg</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox radius="full" debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">full</p>
      </div>
    </div>
  );
}

Debug Mode

Enable debug mode to visualize the hitbox area during development.

import { Checkbox } from "@/components/ui/checkbox";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxDebugDemo() {
  return (
    <div className="flex items-center gap-8">
      <div className="flex flex-col items-center gap-4">
        <Hitbox debug={false}>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">debug=false</p>
      </div>
      <div className="flex flex-col items-center gap-4">
        <Hitbox debug>
          <Checkbox />
        </Hitbox>
        <p className="text-muted-foreground text-sm">debug=true</p>
      </div>
    </div>
  );
}

API Reference

Hitbox

The main hitbox component that extends the clickable area of its child element.

Prop

Type

Sizes

The hitbox provides three predefined sizes:

  • sm: 8px extension - Minimal extension for elements that need slight touch area improvement
  • default: 12px extension - Standard extension that helps most elements meet accessibility requirements
  • lg: 16px extension - Generous extension for dense interfaces or critical interactive elements

Custom Sizes

You can also use custom CSS values for precise control:

<Hitbox size="18px">
  <Checkbox />
</Hitbox>

Accessibility

The Hitbox component improves accessibility by:

  • Larger touch targets: Extends clickable areas to meet minimum touch target size requirements (44px × 44px recommended by WCAG)
  • Better mobile experience: Reduces precision required for touch interactions
  • Maintains semantics: Uses Radix UI's Slot component to preserve the underlying element's accessibility properties
  • Visual feedback: Debug mode helps developers ensure adequate touch target sizes

Touch Target Guidelines

  • Minimum size: 44px × 44px (iOS) or 48dp × 48dp (Android)
  • Recommended size: 48px × 48px or larger
  • Spacing: At least 8px between adjacent touch targets

Size Recommendations

  • Small buttons (32px): Use default size (12px) to reach 56px total target
  • Default buttons (36px): Use default size (12px) to reach 60px total target
  • Large buttons (40px): Use sm size (8px) to reach 56px total target
  • Icon buttons: Use lg size (16px) for maximum accessibility

Best Practices

  • Use larger hitboxes for small interactive elements like checkboxes, icons, or close buttons
  • Consider different positions (top, bottom, left, right) based on surrounding content
  • Test with debug mode enabled to ensure adequate coverage
  • Be mindful of overlapping hitboxes that might interfere with each other