Hitbox
A utility component that extends the clickable area of child elements for improved accessibility and user experience.
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