Monet
@etched/monet 0.0.1

Collapsible

Ported

A text trigger and the content it shows, with a line at the content's start edge. For other layouts, use the parts. Native <div> attributes and ref go to the wrapper.

Kumo source

Strong on clock trees. Wants to stay close to silicon bring-up.

import { Text } from "@etched/monet/text";
import { Collapsible } from "@etched/monet/collapsible";

export function Preview() {
  return (
    <div style={{ inlineSize: "16rem" }}>
      <Collapsible title="View note" defaultOpen>
        <Text as="p">Strong on clock trees. Wants to stay close to silicon bring-up.</Text>
      </Collapsible>
    </div>
  );
}

Installation

import { Collapsible, CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from "@etched/monet/collapsible";

Examples

View note

The all-in-one: a text trigger and the content it shows. The closed note stays mounted, so find in page reaches it and opens it.

Accepted by Sam Lee · Oct 14

import { Text } from "@etched/monet/text";
import { Collapsible } from "@etched/monet/collapsible";

export function ViewNote() {
  return (
    <div style={{ maxInlineSize: "26rem" }}>
      <Text as="p" size="sm" variant="secondary">
        Accepted by Sam Lee · Oct 14
      </Text>
      <Collapsible title="View note">
        <Text as="p">The Ashby owner was wrong. Moved to Kai Ito, who ran the first screen.</Text>
      </Collapsible>
    </div>
  );
}

Unsaved edits

Starts open with defaultOpen, like Orbit's inbox when a send fails and keeps the drafts.

Ana Ruiz

Thanks for the call today. Could you send the tape-out timeline before Friday?

Kai Ito

Moving our chat to Thursday at 10:00. The Taipei trip is still on.

import { Text } from "@etched/monet/text";
import { Collapsible } from "@etched/monet/collapsible";

const drafts = [
  { name: "Ana Ruiz", body: "Thanks for the call today. Could you send the tape-out timeline before Friday?" },
  { name: "Kai Ito", body: "Moving our chat to Thursday at 10:00. The Taipei trip is still on." },
];

export function UnsavedEdits() {
  return (
    <div style={{ maxInlineSize: "26rem" }}>
      <Collapsible title="2 unsaved edits" defaultOpen>
        {drafts.map((draft) => (
          <div key={draft.name}>
            <Text as="p" bold>
              {draft.name}
            </Text>
            <Text as="p" variant="secondary">
              {draft.body}
            </Text>
          </div>
        ))}
      </Collapsible>
    </div>
  );
}

Chevron at the start, in headings

chevron="start" puts the chevron before the title, like a tree, and chevron="none" leaves it out. titleAs puts each trigger in a heading, so screen readers can jump between sections.

import { Text } from "@etched/monet/text";
import { Collapsible } from "@etched/monet/collapsible";

export function ChevronAtTheStartInHeadings() {
  return (
    <div style={{ maxInlineSize: "26rem", display: "grid", gap: "var(--et-space-3)" }}>
      <Collapsible title="Interview loop" chevron="start" titleAs="h3">
        <Text as="p">Four rounds: hiring manager, two technical screens and a team lunch.</Text>
      </Collapsible>
      <Collapsible title="Compensation notes" chevron="start" titleAs="h3">
        <Text as="p">Expects a level above the current offer. Sam Lee to follow up.</Text>
      </Collapsible>
      <Collapsible title="Show 3 older notes" chevron="none">
        <Text as="p">Sep 30 · Sourced from the Hot Chips attendee list.</Text>
      </Collapsible>
    </div>
  );
}

Filters panel

The parts, controlled with open and onOpenChange. The trigger is a Button in the header and the panel sits below it. Done closes the panel and moves focus back to the trigger.

Submitted referrals
import { useRef, useState } from "react";
import { Button } from "@etched/monet/button";
import { Select } from "@etched/monet/select";
import { Text } from "@etched/monet/text";
import { CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from "@etched/monet/collapsible";

export function FiltersPanel() {
  const [open, setOpen] = useState(false);
  const trigger = useRef<HTMLButtonElement>(null);

  return (
    <CollapsibleRoot open={open} onOpenChange={setOpen}>
      <div className="row" style={{ justifyContent: "space-between", maxInlineSize: "32rem" }}>
        <Text variant="heading">Submitted referrals</Text>
        <CollapsibleTrigger ref={trigger} render={<Button size="sm" />}>
          Filters
        </CollapsibleTrigger>
      </div>
      <CollapsiblePanel>
        <div className="row" style={{ alignItems: "end", paddingBlockStart: "var(--et-space-3)" }}>
          <Select label="Status" items={{ new: "New", accepted: "Accepted", declined: "Declined" }} placeholder="Any status" />
          <Select label="Referred by" items={{ sam: "Sam Lee", kai: "Kai Ito", ana: "Ana Ruiz" }} placeholder="Anyone" />
          <Button
            onClick={() => {
              setOpen(false);
              trigger.current?.focus();
            }}
          >
            Done
          </Button>
        </div>
      </CollapsiblePanel>
    </CollapsibleRoot>
  );
}

Table row groups

Each group folds its own rows: the panel renders as a tbody, and the trigger sits in the group row's heading. A row group folds at once, without the height animation.

CandidateRoleReferred by
Ana RuizASIC design engineerSam Lee
José NúñezFirmware engineerKai Ito
import { Table } from "@etched/monet/table";
import { CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from "@etched/monet/collapsible";

interface Referral {
  name: string;
  role: string;
  by: string;
}

const referrals: [string, Referral[]][] = [
  [
    "Active",
    [
      { name: "Ana Ruiz", role: "ASIC design engineer", by: "Sam Lee" },
      { name: "José Núñez", role: "Firmware engineer", by: "Kai Ito" },
    ],
  ],
  [
    "Inactive",
    [
      { name: "Lee Park", role: "Thermal engineer", by: "Sam Lee" },
      { name: "Mira Osei", role: "Physical design lead", by: "Ana Ruiz" },
    ],
  ],
];

export function TableRowGroups() {
  return (
    <div className="card">
      <Table aria-label="Referrals">
        <thead>
          <tr>
            <th style={{ inlineSize: "30%" }}>Candidate</th>
            <th>Role</th>
            <th style={{ inlineSize: "20%" }}>Referred by</th>
          </tr>
        </thead>
        {referrals.map(([group, rows]) => (
          <CollapsibleRoot key={group} defaultOpen={group === "Active"}>
            <tbody>
              <tr>
                <th scope="rowgroup" colSpan={3}>
                  <CollapsibleTrigger>
                    {group} ({rows.length})
                  </CollapsibleTrigger>
                </th>
              </tr>
            </tbody>
            <CollapsiblePanel render={<tbody />}>
              {rows.map((row) => (
                <tr key={row.name}>
                  <th scope="row">{row.name}</th>
                  <td>{row.role}</td>
                  <td>{row.by}</td>
                </tr>
              ))}
            </CollapsiblePanel>
          </CollapsibleRoot>
        ))}
      </Table>
    </div>
  );
}

Accessibility

  • Enter or Space on the trigger opens and closes the panel.
  • The trigger is a button, named by its text (title). With titleAs, it sits in a heading of that level. The panel has no role.
  • The trigger has aria-expanded, and aria-controls while open. A closed panel is hidden from screen readers; find in page opens it (not in a tbody).
  • Set titleAs when the trigger starts a section. When a button in the panel closes it, such as Done, move focus back to the trigger.

API reference

CollapsiblePanel

PropTypeDefaultDescription
classNamestring–

Collapsible

PropTypeDefaultDescription
childrenReactNode–
chevron"end" | "start" | "none""end"Where the chevron sits, or "none".
titleAs"h2" | "h3" | "h4" | "h5" | "h6"–Puts the trigger in a heading, as AccordionItem's titleAs, so screen readers can jump between sections. The heading keeps the type around it. Without it, there is no heading.
title *ReactNode–The trigger's text, such as "View note", as AccordionItem's title.

CollapsibleRoot

PropTypeDefaultDescription
childrenReactNode–

CollapsibleTrigger

PropTypeDefaultDescription
chevron"end" | "start" | "none""end"Where the chevron sits, or "none".
titleAs"h2" | "h3" | "h4" | "h5" | "h6"–Puts the trigger in a heading, as AccordionItem's titleAs, so screen readers can jump between sections. The heading keeps the type around it. Without it, there is no heading.
classNamestring–