Layer card
A card. Without a header row it is one surface. With title or actions it has two layers: the header row on the outer layer and the content on the inner one. Native <div> attributes and ref pass through. Never put a LayerCard inside another one (Kumo design rule): put a heading above the card, or rows inside it.
Next steps
Book the onsite loop for Ana Ruiz with Sam Lee.
import { Button } from "@etched/reticle/button";
import { Text } from "@etched/reticle/text";
import { LayerCard } from "@etched/reticle/layer-card";
export function Preview() {
return (
<LayerCard title="Next steps" actions={<Button size="sm">Edit</Button>} style={{ inlineSize: "16rem" }}>
<Text>Book the onsite loop for Ana Ruiz with Sam Lee.</Text>
</LayerCard>
);
}Installation
import { LayerCard, LayerCardPrimary, LayerCardSecondary } from "@etched/reticle/layer-card";Examples
Details card
title and actions add a header row on the outer layer. The details sit on the inner layer.
Details
- Role
- ASIC design engineer
- Stage
- Interviewing
- Owner
- Sam Lee
- Source
- Referral from Kai Ito
import { type ReactNode } from "react";
import { Button } from "@etched/reticle/button";
import { Pill } from "@etched/reticle/pill";
import { Text } from "@etched/reticle/text";
import { LayerCard } from "@etched/reticle/layer-card";
/** One detail: the term in a fixed column, like Orbit's candidate details. */
function Detail({ term, children }: { term: string; children: ReactNode }) {
return (
<div style={{ display: "grid", gridTemplateColumns: "6rem minmax(0, 1fr)", gap: "var(--et-space-4)" }}>
<Text as="dt" variant="secondary">
{term}
</Text>
<Text as="dd">{children}</Text>
</div>
);
}
export function DetailsCard() {
return (
<LayerCard title="Details" actions={<Button size="sm">Edit</Button>} style={{ maxInlineSize: "26rem" }}>
<dl style={{ display: "grid", gap: "var(--et-space-2)", margin: 0 }}>
<Detail term="Role">ASIC design engineer</Detail>
<Detail term="Stage">
<Pill tone="cyan">Interviewing</Pill>
</Detail>
<Detail term="Owner">Sam Lee</Detail>
<Detail term="Source">Referral from Kai Ito</Detail>
</dl>
</LayerCard>
);
}Category cards are links
render={<a href="…" />} (or a router link) makes the whole card one link, with a hover fill and the focus ring. A link card holds no other buttons or links.
import { Text } from "@etched/reticle/text";
import { LayerCard } from "@etched/reticle/layer-card";
const categories = [
{ slug: "hardware", name: "Hardware", roles: 24, referrals: 9 },
{ slug: "silicon", name: "Silicon", roles: 18, referrals: 6 },
{ slug: "software", name: "Software", roles: 12, referrals: 14 },
{ slug: "operations", name: "Operations", roles: 5, referrals: 2 },
];
export function CategoryCards() {
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(14rem, 1fr))", gap: "var(--et-space-4)" }}>
{categories.map(({ slug, name, roles, referrals }) => (
<LayerCard key={slug} title={name} render={<a href={`#${slug}`} />}>
<Text variant="secondary">
{roles} open roles · {referrals} referrals
</Text>
</LayerCard>
))}
</div>
);
}Setting rows
The setting's name in the header row, its switch at the end, and the help text below.
Swarm LinkedIn sending
When off, Swarm saves messages as drafts instead of sending them on LinkedIn.
Referral reminders
Reminds owners about referrals with no reply after three days.
import { Switch } from "@etched/reticle/switch";
import { Text } from "@etched/reticle/text";
import { LayerCard } from "@etched/reticle/layer-card";
const settings = [
{ name: "Swarm LinkedIn sending", help: "When off, Swarm saves messages as drafts instead of sending them on LinkedIn.", on: true },
{ name: "Referral reminders", help: "Reminds owners about referrals with no reply after three days.", on: false },
];
export function SettingRows() {
return (
<div style={{ display: "grid", gap: "var(--et-space-3)", maxInlineSize: "36rem" }}>
{settings.map(({ name, help, on }) => (
<LayerCard key={name} title={name} actions={<Switch aria-label={name} defaultChecked={on} />}>
<Text variant="secondary">{help}</Text>
</LayerCard>
))}
</div>
);
}Padding sizes
size="sm" (12 by 16 px) for side panels and dense lists; base (16 by 20 px) for the rest. Without a header row, a card is one surface.
Small: 12 by 16 px.
Base: 16 by 20 px.
Reminders
Small, with a header row.
Reminders
Base, with a header row.
import { Text } from "@etched/reticle/text";
import { LayerCard } from "@etched/reticle/layer-card";
export function PaddingSizes() {
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(14rem, 1fr))", gap: "var(--et-space-4)", maxInlineSize: "36rem" }}>
<LayerCard size="sm">
<Text>Small: 12 by 16 px.</Text>
</LayerCard>
<LayerCard>
<Text>Base: 16 by 20 px.</Text>
</LayerCard>
<LayerCard size="sm" title="Reminders">
<Text>Small, with a header row.</Text>
</LayerCard>
<LayerCard title="Reminders">
<Text>Base, with a header row.</Text>
</LayerCard>
</div>
);
}Layers by hand
LayerCardSecondary and LayerCardPrimary are the two layers that title and actions build. Use them for a header row that is not a title.
Run 1284 passed at 71 °C.
import { Text } from "@etched/reticle/text";
import { LayerCard, LayerCardPrimary, LayerCardSecondary } from "@etched/reticle/layer-card";
export function LayersByHand() {
return (
<LayerCard style={{ maxInlineSize: "26rem" }}>
<LayerCardSecondary>Last run</LayerCardSecondary>
<LayerCardPrimary>
<Text>Run 1284 passed at 71 °C.</Text>
</LayerCardPrimary>
</LayerCard>
);
}Table card
size="none" lets a table fill the card; the header row keeps its padding.
Open roles
| Role | Candidates |
|---|---|
| ASIC design engineer | 14 |
| Firmware engineer | 9 |
import { Button } from "@etched/reticle/button";
import { Table } from "@etched/reticle/table";
import { LayerCard } from "@etched/reticle/layer-card";
export function TableCard() {
return (
<LayerCard size="none" title="Open roles" actions={<Button size="sm">Add role</Button>}>
<Table aria-label="Open roles" density="dense">
<thead>
<tr>
<th>Role</th>
<th style={{ inlineSize: "6rem" }}>Candidates</th>
</tr>
</thead>
<tbody>
<tr>
<td>ASIC design engineer</td>
<td>14</td>
</tr>
<tr>
<td>Firmware engineer</td>
<td>9</td>
</tr>
</tbody>
</Table>
</LayerCard>
);
}API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "base" | "none" | "base" | Padding of the content, and of the header row's sides. |
| title | ReactNode | – | Adds a header row with this title above the content. |
| titleAs | "h2" | "h3" | "h4" | "h5" | "h6" | "h2" | The title's element, for its place in the outline. |
| actions | ReactNode | – | Buttons or a switch at the end of the header row, for example <Button size="sm">Edit</Button>. |
| render | ReactElement<{ className?: string }> | – | Makes the whole card one link or one button, with a hover fill and the focus ring: render={<a href="/roles/hardware" />}, a router link, or render={<button type="button" />} with onClick. Such a card holds no buttons, links or fields (and no actions): a link or a button cannot hold another control. |