Dice UI
Components

Hitbox

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

API
import { ShapesIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxDemo() {
  return (
    <div className="flex flex-col gap-8">
      <div className="flex flex-col gap-2">
        <h3 className="font-semibold text-lg">Sizes</h3>
        <div className="flex items-center gap-6">
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox debug>
              <Button>Default</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">default</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox size="sm" debug>
              <Button>Small</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">sm</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox size="lg" debug>
              <Button>Large</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">lg</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox size="5px" debug>
              <Button>Custom</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">5px</p>
          </div>
        </div>
      </div>
 
      <div className="flex flex-col gap-2">
        <h3 className="font-semibold text-lg">Positions</h3>
        <div className="grid grid-cols-2 gap-4 md:grid-cols-4">
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox debug>
              <Button size="sm">All</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">all</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox position="top" debug>
              <Button size="sm">Top</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">top</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox position="bottom" debug>
              <Button size="sm">Bottom</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">bottom</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox position="left" debug>
              <Button size="sm">Left</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">left</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox position="right" debug>
              <Button size="sm">Right</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">right</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox position="vertical" debug>
              <Button size="sm">Vertical</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">vertical</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox position="horizontal" debug>
              <Button size="sm">Horizontal</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">horizontal</p>
          </div>
        </div>
      </div>
 
      <div className="flex flex-col gap-2">
        <h3 className="font-semibold text-lg">Radius</h3>
        <div className="flex items-center gap-6">
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox radius="none" debug>
              <Button>None</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">none</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox radius="sm" debug>
              <Button>Small</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">sm</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox radius="md" debug>
              <Button>Medium</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">md</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox radius="lg" debug>
              <Button>Large</Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">lg</p>
          </div>
          <div className="flex flex-col items-center gap-2.5">
            <Hitbox radius="full" debug>
              <Button size="icon" className="size-8 rounded-full">
                <ShapesIcon />
              </Button>
            </Hitbox>
            <p className="text-muted-foreground text-xs">full</p>
          </div>
        </div>
      </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 after:absolute after:content-['']",
    "[--size-default:0.5rem] [--size-lg:0.75rem] [--size-sm:0.25rem]",
  ],
  {
    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 { Button } from "@/components/ui/button";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxSizesDemo() {
  return (
    <div className="flex items-center gap-6">
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox debug>
          <Button>Default</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">default</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox size="sm" debug>
          <Button>Small</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">sm</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox size="lg" debug>
          <Button>Large</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">lg</p>
      </div>
    </div>
  );
}

Positions

Control which sides of the element the hitbox extends to.

import { Button } from "@/components/ui/button";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxPositionsDemo() {
  return (
    <div className="grid grid-cols-2 gap-4 md:grid-cols-4">
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox debug>
          <Button size="sm">All</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">all</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox position="top" debug>
          <Button size="sm">Top</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">top</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox position="bottom" debug>
          <Button size="sm">Bottom</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">bottom</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox position="left" debug>
          <Button size="sm">Left</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">left</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox position="right" debug>
          <Button size="sm">Right</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">right</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox position="vertical" debug>
          <Button size="sm">Vertical</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">vertical</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox position="horizontal" debug>
          <Button size="sm">Horizontal</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">horizontal</p>
      </div>
    </div>
  );
}

Radius

Control the border radius of the hitbox area.

import { ShapesIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxRadiusDemo() {
  return (
    <div className="flex items-center gap-6">
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox radius="none" debug>
          <Button>None</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">none</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox radius="sm" debug>
          <Button>Small</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">sm</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox radius="md" debug>
          <Button>Medium</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">md</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox radius="lg" debug>
          <Button>Large</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">lg</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox radius="full" debug>
          <Button size="icon" className="size-8 rounded-full">
            <ShapesIcon />
          </Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">full</p>
      </div>
    </div>
  );
}

Custom Size

Use custom CSS values for precise control over the hitbox size.

import { Button } from "@/components/ui/button";
import { Hitbox } from "@/components/components/hitbox";
 
export function HitboxCustomSizeDemo() {
  return (
    <div className="flex items-center gap-6">
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox size="5px" debug>
          <Button size="sm">5px</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">5px</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox size="1rem" debug>
          <Button size="sm">1rem</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">1rem</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox size="20px" debug>
          <Button size="sm">20px</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">20px</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox size="2em" debug>
          <Button size="sm">2em</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">2em</p>
      </div>
    </div>
  );
}

Debug Mode

Enable debug mode to visualize the hitbox area during development.

import { Button } from "@/components/ui/button";
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-2.5">
        <Hitbox debug={false}>
          <Button>Debug Off</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">debug=false</p>
      </div>
      <div className="flex flex-col items-center gap-2.5">
        <Hitbox debug>
          <Button>Debug On</Button>
        </Hitbox>
        <p className="text-muted-foreground text-xs">debug=true</p>
      </div>
    </div>
  );
}

API Reference

Hitbox

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

Prop

Type

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

Best Practices

  • Use larger hitboxes for small interactive elements like 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

Notes

  • The hitbox is implemented using CSS pseudo-elements (::after)
  • Custom sizes accept any valid CSS length value (px, rem, em, %, etc.)
  • The component uses Radix UI's Slot component to avoid wrapper elements
  • Debug mode should only be used during development and testing