Buttons
Button
The action button, in three variants. In a modal footer, the primary action sits rightmost with neutral actions to its left (see ModalActions).
import { Button } from "@silo-code/sdk";
<Button onClick={cancel}>Cancel</Button>
<Button variant="primary" onClick={save}>Save</Button>
<Button variant="danger" onClick={remove}>Delete</Button>
<Button size="sm">Compact</Button>| Prop | Type | Default | Notes |
|---|---|---|---|
variant | "normal" | "primary" | "danger" | "normal" | primary = the accent-filled main action (one per footer); danger = destructive |
size | "normal" | "sm" | "normal" | sm for inline/compact contexts (e.g. a footer start slot) |
| …rest | React.ComponentProps<"button"> | disabled, onClick, etc. pass through |
States (all built in — don't restyle):
- Hover: variant fill shifts one shade (normal buttons shade toward black in dark themes and toward white in light themes; primary/danger always toward black).
- Pressed: a further shade plus a slight scale-down.
- Disabled: 50% opacity, inert — no hover, no press animation, no clicks.
- Focus: the shared 1px inset accent ring. Never add your own.
| Color | Token |
|---|---|
| Normal fill / text | --silo-button-bg / --silo-button-text |
| Primary fill / text | --silo-button-primary-bg (→ accent) / --silo-button-primary-text |
| Danger fill / text | --silo-button-danger-bg (→ err) / --silo-button-danger-text |
| Hover / active fills | derived (color-mix of the variant fill) |
IconButton
A square icon-only button — the kit's one answer for ✕, ⋮, ↻, ✏️, and friends. aria-label is required: the icon is the only visual, so the label is the accessible name.
import { IconButton } from "@silo-code/sdk";
<IconButton aria-label="Refresh" onClick={refresh}>
<RefreshIcon size="1em" />
</IconButton>
// compact — e.g. in a ListRow's trailing slot (one common home, not the only one)
<ListRow
trailing={
<>
<IconButton size="sm" aria-label="Pin" onClick={pin}>
<PinIcon size="1em" />
</IconButton>
<IconButton size="sm" aria-label="More options" onClick={menu}>
<MoreIcon size="1em" />
</IconButton>
</>
}
>
Agent Monitor
</ListRow>
// panel / breadcrumb toolbar (local-web-viewer tone)
<IconButton size="sm" variant="toolbar" aria-label="Back">
<ArrowLeft size="1em" weight="bold" />
</IconButton>| Prop | Type | Default | Notes |
|---|---|---|---|
size | "normal" | "sm" | "normal" | 2.5em / 2em of --silo-font-size-base (Zoom In/Out) |
variant | "normal" | "toolbar" | "normal" | toolbar — B/W toolbar-text icons, hover fill only, no press-scale |
aria-label | string | — | required |
| …rest | button props |
States (normal): transparent at rest (icon at --silo-color-text-lo), bg-hover fill + brighter icon on hover, bg-active fill + scale-down when pressed. toolbar keeps toolbar-text on hover and uses an accent wash when aria-pressed / data-checked is set.
Multiple actions in one row
A ListRow's trailing slot takes several IconButton size="sm" side by side — don't wrap them in extra layout.
Pair it with a Tooltip
aria-label gives screen readers the name; sighted users get nothing from it. Wrap the button in Tooltip so everyone gets the label: <Tooltip content="Refresh"><IconButton aria-label="Refresh">…</IconButton></Tooltip>.
MenuButton
A labelled button that opens a menu — the counterpart to IconButton for cases where a bare ⋮ doesn't tell anyone what they'd get. Renders its label with a trailing chevron, the standard signal that pressing it reveals more rather than performing something.
import { MenuButton } from "@silo-code/sdk";
<MenuButton
label="More"
onClick={(e) =>
ctx.ui.showMenu({
items: [
{ id: "disable", label: "Disable", run: disable },
{ id: "uninstall", label: "Uninstall", run: uninstall },
],
at: e.currentTarget,
})
}
/>;
// variant="field" — a value picker inside a `Section`, when a rich menu
// (icons, checks) beats a native <select>
<Section label="Agent">
<MenuButton
variant="field"
label={agent?.displayName ?? "Auto-detect from the command"}
onClick={(e) => ctx.ui.showMenu({ anchor: e.currentTarget, items })}
>
<AgentIconGlyph icon={agent?.icon} mode="color" colorScheme={scheme} />
</MenuButton>
</Section>;| Prop | Type | Default | Notes |
|---|---|---|---|
label | ReactNode | — | required — the whole point over IconButton |
size | "normal" | "sm" | "normal" | sm for card footers and list rows |
variant | "bare" | "field" | "bare" | field wears Input / Select chrome as a form field |
| children | ReactNode | — | optional leading content, e.g. an icon |
| …rest | button props | aria-haspopup="menu" is applied for you |
Quieter than Button on purpose: bare has no border and no fill at rest, bg-hover on hover. It reveals rather than commits, so it shouldn't compete with the real action beside it. Use variant="field" only when the trigger is the field — a value picker in a form that lines up with Input and Select.
MenuButton or a ⋮ IconButton?
Both open a menu; they differ in who is expected to find it. Reach for MenuButton when the menu is somewhere a user is meant to go — a bare ⋮ is discoverable only by people who already know to look, so anything a first-time user needs belongs behind a labelled trigger. Keep IconButton for dense rows and toolbars where a label genuinely won't fit and the actions are secondary.
Neither owns the menu. Open one from onClick with ctx.ui.showMenu, anchoring at: e.currentTarget so it lines up under the trigger.