Focus Trap
A shadcn-style focus trap utility built with Ark UI primitives.
Keyboard focus stays inside this card.
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { FocusTrap } from "@/components/ui/focus-trap";
import { Input } from "@/components/ui/input";
const FocusTrapDemo = () => {
const [disabled, setDisabled] = useState(false);
return (
<div className="grid w-full max-w-xl gap-4">
<div className="flex flex-wrap gap-2">
<Button type="button" variant="outline">
Outside action
</Button>
<Button
type="button"
variant={disabled ? "default" : "secondary"}
onClick={() => setDisabled((value) => !value)}
>
{disabled ? "Enable trap" : "Disable trap"}
</Button>
</div>
<FocusTrap
className="grid gap-3 rounded-xl border border-border bg-card p-4"
disabled={disabled}
>
<p className="font-medium text-sm">
Keyboard focus stays inside this card.
</p>
<Input aria-label="Project name" placeholder="Project name" />
<div className="flex flex-wrap gap-2">
<Button type="button">Save</Button>
<Button type="button" variant="outline">
Cancel
</Button>
</div>
</FocusTrap>
</div>
);
};
export default FocusTrapDemo;
Installation
npx shadcn@latest add @ark-cn/focus-trapInstall the dependency required by this utility:
npm install @ark-ui/reactCopy the utility source into your app:
TSXcomponents/ui/focus-trap.tsx
"use client";
import {
FocusTrap as FocusTrapPrimitive,
type FocusTrapProps as FocusTrapPrimitiveProps,
} from "@ark-ui/react/focus-trap";
import { cn } from "@/lib/utils";
export type FocusTrapProps = FocusTrapPrimitiveProps;
export const FocusTrap = ({ className, ...props }: FocusTrapProps) => (
<FocusTrapPrimitive
className={cn(className)}
data-slot="focus-trap"
{...props}
/>
);
Update import aliases to match your project setup.
Usage
import { FocusTrap } from "@/components/ui/focus-trap"Examples
Default
Keyboard focus stays inside this card.
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { FocusTrap } from "@/components/ui/focus-trap";
import { Input } from "@/components/ui/input";
const FocusTrapDemo = () => {
const [disabled, setDisabled] = useState(false);
return (
<div className="grid w-full max-w-xl gap-4">
<div className="flex flex-wrap gap-2">
<Button type="button" variant="outline">
Outside action
</Button>
<Button
type="button"
variant={disabled ? "default" : "secondary"}
onClick={() => setDisabled((value) => !value)}
>
{disabled ? "Enable trap" : "Disable trap"}
</Button>
</div>
<FocusTrap
className="grid gap-3 rounded-xl border border-border bg-card p-4"
disabled={disabled}
>
<p className="font-medium text-sm">
Keyboard focus stays inside this card.
</p>
<Input aria-label="Project name" placeholder="Project name" />
<div className="flex flex-wrap gap-2">
<Button type="button">Save</Button>
<Button type="button" variant="outline">
Cancel
</Button>
</div>
</FocusTrap>
</div>
);
};
export default FocusTrapDemo;
API reference
This utility mirrors the upstream Ark UI primitive.
See the ARK UI documentation for the full API.