Disclosure
SM, under 950B gzippedA single collapsible section.
- Category
- Data display
- Budget
- sm, single-purpose control
- Runtime deps
- None
Preview
This is the exact fixture the conformance suite renders in CI. Switch the language to see the component mirror and reformat.
Install
npx shadcn@latest add https://kata-ui-rho.vercel.app/r/disclosure.jsonCopies the source into your project. Pulls in 1 primitive: cn.
Source
"use client";
import { useId, useState } from "react";
import { cn } from "../lib/cn";
export interface DisclosureProps {
summary: string;
children: React.ReactNode;
defaultOpen?: boolean;
className?: string;
}
/** A single collapsible section. For more than one, related, see `Accordion`. */
export function Disclosure({ summary, children, defaultOpen = false, className }: DisclosureProps) {
const [open, setOpen] = useState(defaultOpen);
const id = useId();
return (
<div className={cn("rounded-lg border border-neutral-200 dark:border-neutral-800", className)}>
<button
type="button"
aria-expanded={open}
aria-controls={id}
onClick={() => setOpen((v) => !v)}
className="flex w-full items-center justify-between px-4 py-3 text-start text-sm font-medium hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-blue-600 dark:hover:bg-neutral-900"
>
{summary}
<span aria-hidden="true" className={cn("transition-transform motion-reduce:transition-none", open && "rotate-180")}>
▾
</span>
</button>
{open && (
<div id={id} className="px-4 pb-4 text-sm text-neutral-700 dark:text-neutral-300">
{children}
</div>
)}
</div>
);
}
What CI checks
- Bundled, minified and gzipped against its tier budget
- Audited by axe in the state previewed above
- Rendered through react-dom/server with no browser globals
- Scanned for network calls, dangerous sinks, and unguarded animation