Monet
@etched/monet 0.0.1

Item

Ported

A row: media at the start, a title and a description, and actions at the end. Put rows in an ItemGroup, or use one on its own. With render, the whole row is a link or a button.

Ana RuizASIC design engineerInterviewing
import { Avatar } from "@etched/monet/avatar";
import { Pill } from "@etched/monet/pill";
import { Item } from "@etched/monet/item";

export function Preview() {
  return (
    <div style={{ inlineSize: "20rem" }}>
      <Item
        variant="outline"
        size="sm"
        media={<Avatar name="Ana Ruiz" size="sm" alt="" />}
        title="Ana Ruiz"
        description="ASIC design engineer"
        actions={<Pill tone="cyan">Interviewing</Pill>}
      />
    </div>
  );
}

Installation

import { Item, ItemGroup } from "@etched/monet/item";

Examples

Media, text and actions

An icon gets a filled box; an avatar shows as it is. The description stops after two lines.

Weekly digestA summary of new referrals, stage changes and interviews, sent every Monday at 09:00 in your time zone.
Kai ItoHiring manager, Physical design
import { BellIcon } from "@phosphor-icons/react";
import { Avatar } from "@etched/monet/avatar";
import { Button } from "@etched/monet/button";
import { Item } from "@etched/monet/item";

const panel = { inlineSize: "100%", maxInlineSize: "30rem" };

export function Anatomy() {
  return (
    <div style={{ ...panel, display: "grid", gap: "var(--et-space-2)" }}>
      <Item
        media={<BellIcon />}
        title="Weekly digest"
        description="A summary of new referrals, stage changes and interviews, sent every Monday at 09:00 in your time zone."
        actions={<Button size="sm">Edit</Button>}
      />
      <Item
        media={<Avatar name="Kai Ito" alt="" />}
        title="Kai Ito"
        description="Hiring manager, Physical design"
        actions={
          <Button size="sm" variant="ghost">
            Message
          </Button>
        }
      />
    </div>
  );
}

Outline and sizes

outline gives a row its own edge. sm is denser, with a smaller icon box.

API keyCreated Oct 2 by Sam Lee. Last used today.
Webhook secretCreated Sep 18 by Kai Ito.
import { KeyIcon } from "@phosphor-icons/react";
import { Button } from "@etched/monet/button";
import { Item } from "@etched/monet/item";

const panel = { inlineSize: "100%", maxInlineSize: "30rem" };

export function OutlineAndSizes() {
  return (
    <div style={{ ...panel, display: "grid", gap: "var(--et-space-2)" }}>
      <Item variant="outline" media={<KeyIcon />} title="API key" description="Created Oct 2 by Sam Lee. Last used today." actions={<Button size="sm">Rotate</Button>} />
      <Item variant="outline" size="sm" media={<KeyIcon />} title="Webhook secret" description="Created Sep 18 by Kai Ito." actions={<Button size="sm">Rotate</Button>} />
    </div>
  );
}

A group with separators

ItemGroup is a list: screen readers announce it and its count. separators draws a line between rows. The app puts the group in its own panel.

  • Ana RuizASIC design engineerInterviewing
  • José NúñezFirmware engineerScheduled
  • Mira OseiPhysical design leadHired
import { Avatar } from "@etched/monet/avatar";
import { Pill, type PillTone } from "@etched/monet/pill";
import { Item, ItemGroup } from "@etched/monet/item";

const panel = { inlineSize: "100%", maxInlineSize: "30rem" };

const team: [string, string, string, PillTone][] = [
  ["Ana Ruiz", "ASIC design engineer", "Interviewing", "cyan"],
  ["José Núñez", "Firmware engineer", "Scheduled", "purple"],
  ["Mira Osei", "Physical design lead", "Hired", "green"],
];

export function GroupWithSeparators() {
  return (
    <div className="card" style={panel}>
      <ItemGroup separators aria-label="Referrals">
        {team.map(([name, role, stage, tone]) => (
          <Item key={name} size="sm" media={<Avatar name={name} size="sm" alt="" />} title={name} description={role} actions={<Pill tone={tone}>{stage}</Pill>} />
        ))}
      </ItemGroup>
    </div>
  );
}

Accessibility

  • With render, the row is one link or button: Enter follows a link row, and Enter or Space presses a button row.
  • A row without render is a div with no role. A link or button row is named by all its text in order: title, description, then actions.
  • ItemGroup is a list (role="list", so Safari keeps it) with one list item per row, so screen readers say how many rows there are.
  • A link or button row holds no other controls: put a chevron or a badge in actions. Set aria-label for a shorter name.

API reference

ItemGroup

PropTypeDefaultDescription
separatorsbooleanfalseA line between rows, with no gap. Without it, rows have a small gap, for outline rows.
children *ReactNode–Items. Each goes in its own list item, so put them directly inside, not in a fragment.

Item

PropTypeDefaultDescription
title *ReactNode–The first line, such as a name or a setting. It names the row.
descriptionReactNode–The second line, in the subtle ink. Long text stops after two lines.
mediaReactNode–An icon or an Avatar at the start. An icon gets a filled box.
actionsReactNode–Buttons, a badge or a pill at the end. A row that is a link or a button holds no other controls: use a chevron or a badge here, never a button.
variant"plain" | "outline""plain"
size"base" | "sm""base"
renderReactElement<{ className?: string }>–Makes the whole row another element with the row's look: a link, render={<a href="/settings/team" />} or a router link, or a button, render={<button type="button" />}. Then the row takes the hover fill and the focus ring. Its accessible name is its text, in order; set aria-label for a shorter one.