Hitbox
A utility component that extends the clickable area of child elements for improved accessibility and user experience.
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 improvementdefault
: 12px extension - Standard extension that helps most elements meet accessibility requirementslg
: 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