COMPONENTS/DATA DISPLAY

Collapsible.

Details, on your terms.

In practice

Use open/onOpenChange for controlled state. Trigger and content are automatically associated.

Installation

Install Coal once. Components and TypeScript declarations are included. React 19 and React DOM 19 are peer dependencies.

Terminal · local package
npm install http://localhost:3100/downloads/chlohal-coal-ui-0.5.0.tgz

This installs the local package. It is not yet published on npm. See installation for stylesheet setup and integration.

Usage

example.tsx
"use client";
import * as React from "react";
import "@chlohal/coal-ui/styles.css";
import { ChevronDown } from "lucide-react";
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@chlohal/coal-ui";
export default function Example() {
    return (<Collapsible>
          <CollapsibleTrigger>
            A few more details <ChevronDown size={14}/>
          </CollapsibleTrigger>
          <CollapsibleContent>
            Built with React and Base UI. Styled with Tailwind. Yours to change.
          </CollapsibleContent>
        </Collapsible>);
}

Source

The original Coal implementation. Use the package export to integrate it.

packages/react/src/collapsible.tsx
"use client";
import * as React from "react";
import {
  Action,
  cn,
  useValue,
  useRequired,
  type ActionProps,
} from "./internal.js";
const Context = React.createContext<
  { open: boolean; set: (v: boolean) => void; id: string } | undefined
>(undefined);
export type CollapsibleProps = React.ComponentPropsWithRef<"div"> & {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (value: boolean) => void;
};
export function Collapsible({
  open,
  defaultOpen = false,
  onOpenChange,
  children,
  ...props
}: CollapsibleProps) {
  const [v, set] = useValue(open, defaultOpen, onOpenChange);
  const id = React.useId();
  return (
    <Context.Provider value={{ open: v, set, id }}>
      <div {...props}>{children}</div>
    </Context.Provider>
  );
}
export function CollapsibleTrigger({ onClick, ...props }: ActionProps) {
  const c = useRequired(Context, "CollapsibleTrigger");
  return (
    <Action
      {...props}
      aria-expanded={c.open}
      aria-controls={c.id}
      onClick={(e) => {
        onClick?.(e);
        if (!e.defaultPrevented) c.set(!c.open);
      }}
    />
  );
}
export function CollapsibleContent({
  className,
  ...props
}: React.ComponentPropsWithRef<"div">) {
  const c = useRequired(Context, "CollapsibleContent");
  return (
    <div
      {...props}
      id={c.id}
      hidden={!c.open}
      className={cn("coal-collapsible-content", className)}
    />
  );
}

Accessibility checklist

  • Provide an accessible name for controls, including icon-only buttons.
  • Check keyboard navigation, visible focus and disabled states in your application.
  • Do not rely on color alone to communicate status.