# Status Component Group

> Collapsible group wrapper for status components

A shadcn/ui block from the [@openstatus](https://openstatus.dev/registry) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/openstatus/status-component-group).

- Page, with a live preview: https://shadcn.exchange/r/openstatus/status-component-group
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/openstatus/status-component-group-light-8b6751992af511a0.png
- Registry item: https://openstatus.dev/r/status-component-group.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://openstatus.dev/r/status-component-group.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://openstatus.dev/r/status-component.json`, `https://openstatus.dev/r/status-types.json`, `collapsible`

## Files

### `src/components/blocks/status-component-group.tsx`

````tsx
"use client";

import {
  StatusComponentIcon,
  StatusComponentStatus,
} from "@/components/blocks/status-component";
import type { StatusType } from "@/components/blocks/status.types";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { useEffect, useState } from "react";

/**
 * StatusComponentGroup - Collapsible group for organizing related monitors
 *
 * A collapsible container component for grouping related StatusComponent items,
 * displaying an aggregate status for the group. The component shows:
 * - Group title
 * - Aggregate status label (e.g., "Operational", "Degraded")
 * - Status icon with appropriate color
 * - Expandable/collapsible content area for grouped monitors
 *
 * **Key Features**:
 * - **Collapsible**: Click to expand/collapse grouped monitors
 * - **Hydration-Aware Animations**: Prevents layout shift on initial render by delaying
 *   animations until after mount (avoids animation flash when defaultOpen=true)
 * - **Aggregate Status**: Shows overall status for the entire group
 * - **Keyboard Accessible**: Full keyboard support via Radix UI Collapsible
 * - **Hover Effects**: Subtle border and background changes on hover/open
 *
 * The component uses a hydration-aware animation pattern: animations are disabled
 * on the server and first client render, then enabled after mount. This prevents
 * layout shifts when `defaultOpen={true}` is used.
 *
 * @param title - The group name/title
 * @param status - The aggregate status type for the group (controls icon color and status label)
 * @param defaultOpen - Whether the group starts expanded (default: false)
 * @param children - StatusComponent items or other content to display when expanded
 *
 * @example
 * // Basic collapsible group with multiple monitors
 * ```tsx
 * <StatusComponentGroup
 *   title="API Services"
 *   status="success"
 *   defaultOpen={false}
 * >
 *   <StatusComponent variant="success">
 *     <StatusComponentHeader>
 *       <StatusComponentHeaderLeft>
 *         <StatusComponentIcon />
 *         <StatusComponentTitle>REST API</StatusComponentTitle>
 *       </StatusComponentHeaderLeft>
 *     </StatusComponentHeader>
 *   </StatusComponent>
 *   <StatusComponent variant="success">
 *     <StatusComponentHeader>
 *       <StatusComponentHeaderLeft>
 *         <StatusComponentIcon />
 *         <StatusComponentTitle>GraphQL API</StatusComponentTitle>
 *       </StatusComponentHeaderLeft>
 *     </StatusComponentHeader>
 *   </StatusComponent>
 * </StatusComponentGroup>
 * ```
 *
 * @example
 * // Degraded group with mixed statuses
 * ```tsx
 * <StatusComponentGroup
 *   title="Database Cluster"
 *   status="degraded"
 *   defaultOpen={true}
 * >
 *   <StatusComponent variant="success">
 *     <StatusComponentHeader>
 *       <StatusComponentHeaderLeft>
 *         <StatusComponentIcon />
 *         <StatusComponentTitle>Primary DB</StatusComponentTitle>
 *       </StatusComponentHeaderLeft>
 *     </StatusComponentHeader>
 *   </StatusComponent>
 *   <StatusComponent variant="degraded">
 *     <StatusComponentHeader>
 *       <StatusComponentHeaderLeft>
 *         <StatusComponentIcon />
 *         <StatusComponentTitle>Replica DB</StatusComponentTitle>
 *       </StatusComponentHeaderLeft>
 *     </StatusComponentHeader>
 *   </StatusComponent>
 * </StatusComponentGroup>
 * ```
 *
 * @example
 * // Multiple groups for different service categories
 * ```tsx
 * <StatusContent>
 *   <StatusComponentGroup title="Core Services" status="success">
 *     // Core service monitors...
 *   </StatusComponentGroup>
 *   <StatusComponentGroup title="Third-Party APIs" status="degraded" defaultOpen>
 *     // Third-party monitors...
 *   </StatusComponentGroup>
 *   <StatusComponentGroup title="Internal Tools" status="success">
 *     // Internal tool monitors...
 *   </StatusComponentGroup>
 * </StatusContent>
 * ```
 *
 * @see StatusComponent - For individual monitor displays within the group
 * @see StatusComponentIcon - For the status icon displayed in the header
 * @see StatusComponentStatus - For the status label displayed in the header
 */
export function StatusComponentGroup({
  children,
  title,
  status,
  className,
  defaultOpen = false,
  ...props
}: React.ComponentProps<"div"> & {
  title: string;
  status?: Exclude<StatusType, "empty">;
  children?: React.ReactNode;
  defaultOpen?: boolean;
}) {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  return (
    <div {...props}>
      <Collapsible
        defaultOpen={defaultOpen}
        data-slot="status-component-group"
        className={cn(
          "-mx-3",
          "bg-muted/50 hover:border-border/50 data-[state=open]:border-border/50 data-[state=open]:bg-muted/50 rounded-lg border border-transparent",
          className,
        )}
      >
        <CollapsibleTrigger
          data-slot="status-component-group-trigger"
          className={cn(
            "group/component flex w-full items-center justify-between gap-2 rounded-lg px-3 py-2 font-mono font-medium",
            "cursor-pointer",
            className,
          )}
          data-variant={status}
          aria-label={`Toggle ${title} status details`}
        >
          {title}
          <div className="flex items-center gap-2">
            <StatusComponentStatus className="text-sm" />
            <StatusComponentIcon />
          </div>
        </CollapsibleTrigger>
        <CollapsibleContent
          data-slot="status-component-group-content"
          data-animate={mounted}
          className={cn(
            "border-border/50 flex flex-col gap-3 border-t px-3 py-2",
            "overflow-hidden",
            // REMINDER: otherwise, if defaultOpen is true, the animation will be triggered and we have a layout shift
            "data-[animate=true]:data-[state=closed]:animate-collapsible-up data-[animate=true]:data-[state=open]:animate-collapsible-down",
          )}
        >
          {children}
        </CollapsibleContent>
      </Collapsible>
    </div>
  );
}
StatusComponentGroup.displayName = "StatusComponentGroup";
````
