Every icon size, hit box, colour, effect and prohibition in one place — rendered live at true scale.
Library: lucide-react ^0.575.0. Everything on this page is scaffold (hand-drawn), not a rendered REGION.
strokeWidth 2 in COLORS.textMuted so only size varies.| Constant | Value | Source | Consumed by |
|---|---|---|---|
| HEADER_ICON_SIZE | 11 | paneConstants.js:10 | SSOT for section-header glyphs — HeaderAction, HeaderCopyButton, collapsed detach, DatabasePlusCombo |
| HEADER_ICON_SIZE dup | 11 | studio tableViewConstants.js:10 | data-viewer header icons — duplicate, should import the shared token |
| GAP_MARKER_ICON_SIZE | 11 | tableViewConstants.js:12 | grid gap markers |
| STATUS_ICON_SIZE | 12 | nodeStatusConfig.js:3 | STATUS NODE ICON |
| BasePortIcon size | 12 | PortIcons.jsx:4 | DataInPortIcon / DataOutPortIcon |
| StateGlyph size | 12 | StatePortBadge.jsx:29 | STATE-PORT badges (strokeWidth 3) |
| ICON_SZ | 12 | RepoTree.jsx:14, BranchList.jsx:30 | landing lists |
| TREE_ICON_SIZE | 14 | treeChromeTokens.js:10 | every control glyph in a tree row |
| ICON_SIZE / ICON_SZ | 14 | FolderTreeNode.jsx:17, CapacitiesTreeRows.jsx:11 | folder + capability trees |
| breadcrumb separator | 14 | styleTokens.js:30 | topbar breadcrumb chevrons (OFFLINE_GRAY, margin 0 5px) |
| Dropdown chevron | 14 | controls.md:201 | fixed chrome affordance — exempt from the ratio helper |
| ToolbarIcon size | 16 | ToolbarIcon.jsx:15 | section-header toolbars — “VSC-style: 16px glyph in a 22×22 button” |
| ICON_PROPS.size | 48 | login landing/icons.jsx:4 | login landing tiles (strokeWidth 1.5) |
| module tile viewBox | 56×56 | frontend-styling.md:164 | module tile icons — inline SVG only, never Lucide |
| Helper | Rule | Source |
|---|---|---|
| iconSize(icon, height) | max(1, round(iconRatioFor(icon) × height)) | controlShell.js:48 |
| DEFAULT_ICON_RATIO | 0.6 | controlShell.js:8 |
| JoinIcons iconRatio | 1.2 → ≈29px at a 24px control | JoinIcons.jsx:85 |
| Composite add badge | badge = round(HEADER_ICON_SIZE × 0.6) = 7 · offset = −round(× 0.2) = −2 | useTemplateSidePanelItems.jsx:38 |
| Icon↔label gap | CONTROL_ICON_GAP 6 (sans) · 5 (mono) · SegmentedToggle ICON_GAP 5 | controlTokens.js:19,71 |
The ratio is keyed on the icon component reference — never a name (controls.md:179).
overflow: visible on icon-bearing controls: height is a floor, not a cap (controls.md:199).
| Shell | Box | Radius | Resting glyph | Source |
|---|---|---|---|---|
| IconButton tile | padding 3 (auto size) | 6 | COLORS.text #111827 | IconButton.jsx:31 |
| IconButton toolbar | padding 6 (auto size) | 6 | COLORS.text #111827 | IconButton.jsx:32 |
| IconButton header | h 22 · padding 0 6 | 4 | COLORS.textMuted #6b7280 | IconButton.jsx:33 |
| ToolbarIcon | 22 × 22 | 4 | COLORS.textMuted | ToolbarIcon.jsx:34 |
| TREE_ICON_BOX | 22 × 22 · padding 0 | — | MUTED | treeChromeTokens.js:13 |
| HEADER_ICON_BTN_BASE | padding 6 | 4 | #374151 | styleTokens.js:36 |
| Mapping connection dot | ~28px transparent hit pad | — | — | ui-definitions.md:672 |
| Button, icon-only | square at standard height (24 mono) | 3 mono | — | controls.md:222 |
controlBaseStyle is applied but height/minHeight/gap are explicitly nulled
(IconButton.jsx:110) so the sans 28px height does not reach an IconButton — the shell is padding-sized except in header.
The framed 1px border is absorbed by box-sizing: border-box, so it never grows the 22px header.
| State | tile / toolbar | header | Source |
|---|---|---|---|
| Resting | COLORS.text #111827 | COLORS.textMuted #6b7280 | IconButton.jsx:72 |
| Hover | unchanged — hover is a background change only, unless hoverColor is passed | IconButton.jsx:88 | |
| Active / pressed | activeColor ?? hoverColor ?? resting · bg = activeBg ?? hoverBg | IconButton.jsx:87 | |
| Disabled | colour unchanged · opacity 0.4 | #f3f4f6 · opacity 0.45 | IconButton.jsx:73,76 |
| Transient success | Check @ #16a34a for ~1500ms via useTransientFlag | HeaderCopyButton.jsx:29 | |
The header disabled treatment is a deliberate exception to the stock DISABLED_OPACITY path, kept to preserve the historical header look.
Runtime MODULE_COLORS (logTheme.js:30) differs for MENTOR (#059669) and adds SUPER #d97706.
ICON_HOVER_BG = rgba(0,0,0,0.06) (colors.js:14) — translucent, so one token works on every surface.
A light fill would render as a white box on the grey header. When the resting fill is an opaque colour that must survive, compose with
iconHoverOver(resting) → linear-gradient(HOVER,HOVER), resting.
| Effect | Value | Source |
|---|---|---|
| IconButton background | transition: background 120ms ease | IconButton.jsx:27 |
| ToolbarIcon background | transition: background 0.1s | ToolbarIcon.jsx:38 |
| SegmentedToggle slide | SEGMENTED_TOGGLE_ANIM_MS = 200 | controlTokens.js:41 |
| Refresh spinner | spin 1s linear infinite | frontend-styling.md:138 |
| Transient success Check | ~1500 ms | frontend-styling.md:134 |
| Disabled opacity | DISABLED_OPACITY 0.4 · header override 0.45 | themeTokens.js:19 |
| Framed border | 1px solid COLORS.border, else transparent (header only) | IconButton.jsx:74 |
| Knockout ring | --venact-header-action-bg pinned to COLORS.bg — never re-pointed at hover | IconButton.jsx:126 |
| Cursor | pointer when interactive, else default arrow — venact has no not-allowed | cursors.md:86 |
| Focus | native only — real <button type="button">, no custom ring | IconButton.jsx:90 |
| Hover mechanism | onMouseEnter/Leave + useState via useHoverState — never a CSS pseudo-class | controlShell.js:25 |
| Width | Where |
|---|---|
| 1 | JoinIcons circles · admin diagonal background · DetailGraph thresholds |
| 1.5 | The documented standard for inline SVG · login landing icons · canvas group icons · edges · charts |
| 2 | Port icons · SidebarTreeNode · TileShell · RunNodeIcon · ColumnsPod · markdown copy/check |
| 2.5 | Combobox chevron · ContextMeter ring · mapping + alert edges |
| 3 | Plus badges · StateGlyph · Hourglass/Loader2 in StatePortBadge · DataNode / AuthNode Plus |
Inline SVG convention (frontend-styling.md:159): xmlns required, fill="none",
strokeWidth="1.5", stroke #b0bec5, strokeLinecap/Linejoin="round".
Lucide is fine inside components — never for module tile icons.
COLORS.bg, COLORS.bgAlt, or any near-white hex.hoverBg override must be no lighter than every surface that control renders on.iconHoverOver().opacity mirroring the disabled prop — the shell owns it.isRefreshing + .spin — every RefreshCw control uses RefreshButton.Check with setState + setTimeout — use useTransientFlag.IconButton (or Button variant="subtle" + icon, no label).--venact-header-action-bg is deliberately not re-pointed at the hover surface.iconSize.Tag / IconButton — they are on the sans resolver.not-allowed, wait, or progress cursor anywhere in venact.aria-label mirrors title on every header action — title={t} aria-label={t}.<button type="button"> elements, so they are keyboard-focusable and activatable.aria-hidden="true".title || (collapseLabel ? label : undefined).aria-disabled greys a control but keeps it clickable, so the click can surface an explanatory popup — native disabled stays unset.CONTROL_H_TOUCH = 44 is the mobile floor for the sans family — but IconButton nulls height/minHeight, so the floor never actually reaches it.pointer: fine); on touch the ReadOnlyBadge carries the mode instead.SECTION_HEADER_HEIGHT 22, TREE_ROW_HEIGHT 22, TAB_BAR_HEIGHT 22,
ToolbarIcon 22×22 and CONTROL_H_COMPACT 22 are all 2px under the 24px floor —
and the studio tab-bar panel toggles are bare <div onClick>s with no aria-label,
no focus, and no hover treatment at all.