Config Popup Style Guide¶
Reference for building tabs and components within the Toolbox config overlay (ConfigOverlay / WindowTabs).
Design Tokens¶
All values come from extension/data/css/base.css. Use variables where they exist; use raw rgb() only for values not covered by a token.
Token |
Value |
Usage |
|---|---|---|
|
Verdana, Arial, Helvetica, sans-serif |
All text |
|
|
Tab footer bar background |
|
|
Sidebar tab background |
|
|
Sidebar tab hover / active |
|
|
Input borders at rest |
|
|
Input focus ring; “add” card border |
|
|
Non-primary |
|
|
Card body background; non-primary button |
|
|
Non-primary button text |
|
|
Window drop-shadow |
Raw Hex Palette¶
These are used directly in component CSS (no token exists for them yet).
Name |
Hex |
Usage |
|---|---|---|
Body text |
|
Card titles, field labels, preview text |
Muted text |
|
Section headers, hints, badge text |
Placeholder / empty |
|
Untitled italics, empty-state text, drag handles |
Card border |
|
All card / section borders |
Card body bg |
|
Card background |
Card header bg |
|
Card header strip; “add” card full background |
Grouped field bg |
|
Action-group grid background |
Badge bg |
|
Context badge fill |
Primary blue |
|
Primary button; active icon button; expand-toggle link |
Primary blue hover |
|
Primary button hover border |
Primary blue active |
|
Primary button pressed |
Disabled primary |
|
Primary button disabled fill |
Warning bg |
|
Warning banner background |
Warning border |
|
Warning banner border |
Warning accent |
|
Warning banner left accent |
Warning text |
|
Warning banner text |
Error text |
|
Validation / error messages |
Typography¶
Role |
Size |
Weight |
Color |
Notes |
|---|---|---|---|---|
Section title |
11px |
bold |
|
Uppercase, |
Field label |
11px |
bold |
|
— |
Sub-group label |
10px |
bold |
|
Uppercase, |
Card title |
12px |
bold |
|
— |
Body / preview |
12px |
normal |
|
|
Hint / description |
11px |
normal |
|
Italic |
Empty state |
11px |
normal |
|
Italic |
Badge |
10px |
bold |
|
|
Link / toggle |
11px |
normal |
|
Underline on hover |
Spacing¶
Context |
Value |
|---|---|
Tab content padding |
|
Card list gap |
|
Card header padding |
|
Card body / preview padding |
|
Edit form padding |
|
Edit form field gap |
|
Field label → input gap |
|
Section margin-bottom |
|
Section title margin-bottom |
|
Action group grid gap |
|
Edit button row gap |
|
Icon button gap |
|
Components¶
Card List¶
A vertically stacked list of items. Used for both editable lists and sort lists.
.root {
display: flex;
flex-direction: column;
gap: 8px;
}
.cardList {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
}
Card (Editable)¶
Two zones: a header strip and a collapsible body.
.card {
border: 1px solid #d0dce8;
border-radius: 3px;
background: #f7fafd;
overflow: hidden;
}
/* "new item" variant */
.addCard {
background: #eaf2fa;
border-color: var(--toolbox-input-focus);
}
.cardHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px;
background: #eaf2fa;
border-bottom: 1px solid #d0dce8;
gap: 8px;
}
.cardTitle {
font-size: 12px;
font-weight: bold;
color: #46596d;
flex: 1;
min-width: 0; /* enables text-overflow inside flex */
}
.untitled {
color: #9fbad6;
font-weight: normal;
}
Card header slot order: title (flex: 1) → badges → actions
Card (Sort / Drag)¶
Flat single-row card used in sort lists. No header strip — everything is inline.
.card {
display: flex;
align-items: center;
gap: 6px;
border: 1px solid #d0dce8;
border-radius: 3px;
background: #f7fafd;
padding: 5px 8px;
}
Drag handle uses cursor: grab / cursor: grabbing, color #9FBAD6, Material Icons dragHandle icon at 18px.
Context Badges¶
Shown in the card header to indicate which contexts an item applies to.
.contextBadges {
display: flex;
gap: 3px;
flex-shrink: 0;
}
.contextBadge {
font-size: 10px;
font-weight: bold;
color: #6b7a8d;
background: #d8e8f5;
border-radius: 2px;
padding: 1px 4px;
letter-spacing: 0.02em;
}
Preview Area¶
Shown in a card body when not editing. Clamp to 3 lines by default; “Show more / Show less” toggle appears when content overflows.
.previewWrap {
padding: 6px 8px;
}
.previewClamped {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
font-size: 12px;
color: #46596d;
line-height: 1.4;
}
.previewFull {
font-size: 12px;
color: #46596d;
line-height: 1.4;
}
.expandToggle {
font-size: 11px;
color: #3b7db4; /* underline on hover */
}
.noText {
font-size: 11px;
color: #9fbad6;
font-style: italic;
padding: 6px 8px;
}
Edit Form (inside a card)¶
.editForm {
display: flex;
flex-direction: column;
gap: 10px;
padding: 8px;
width: 100%;
}
.editField {
display: flex;
flex-direction: column;
gap: 3px;
}
/* All inputs in a field should be full-width */
.editField input[type="text"],
.editField select,
.editField textarea {
box-sizing: border-box;
width: 100%;
}
.editFieldLabel {
font-size: 11px;
font-weight: bold;
color: #46596d;
}
.editButtons {
display: flex;
gap: 6px;
}
Always use <TextInput>, <TextareaInput>, <ActionSelect>, and <CheckboxInput> from shared/controls/ — never raw <input> / <select> / <textarea>.
Action Group Grid (checkbox clusters)¶
Used for grouping boolean toggles (e.g. mod macro actions).
.actionsGrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
padding: 8px;
border: 1px solid #d0dce8;
border-radius: 2px;
background: #f0f5fa;
}
.actionGroup {
display: flex;
flex-direction: column;
gap: 5px;
}
.actionGroupLabel {
font-size: 10px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #6b7a8d;
padding-bottom: 3px;
border-bottom: 1px solid #d0dce8;
}
Section Grouping (flat-form tabs)¶
For tabs that present a flat form rather than a card list.
.section {
margin-bottom: 18px;
}
.section:last-child {
margin-bottom: 0;
}
.sectionTitle {
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #6b7a8d;
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 1px solid #d0dce8;
}
.fieldLabel {
display: block;
font-size: 11px;
color: #46596d;
margin-bottom: 3px;
margin-top: 10px;
}
.fieldHint {
font-size: 11px;
color: #6b7a8d;
font-style: italic;
}
Radio Option Cards¶
For mutually exclusive settings choices (e.g. reply type in removal reasons).
.radioOption {
padding: 7px 10px;
border: 1px solid var(--toolbox-input-border);
border-radius: 2px;
background: #f7fafd;
}
.radioOption.selected {
border-color: var(--toolbox-input-focus);
background: #eaf2fa;
}
.radioLabel {
display: flex;
align-items: center;
gap: 7px;
cursor: pointer;
font-size: 12px;
}
/* Sub-options indented inside the selected card */
.subOptions {
margin-top: 8px;
margin-left: 22px;
padding-top: 7px;
border-top: 1px solid #d0dce8;
}
Interaction Patterns¶
saveRef / resetRef / addRef¶
Cross-boundary imperative callbacks — a component exposes a function to its parent (dom.tsx) without prop-drilling React state. Always use the stale-closure-safe pattern:
const handleSaveRef = useRef<() => void>(() => {},)
handleSaveRef.current = handleSave // updated every render
useEffect(() => {
if (!saveRef) { return }
saveRef.current = () => handleSaveRef.current()
return () => {
saveRef.current = null
}
}, [],) // wired once
disabledRef¶
Same pattern, but carries a state setter:
type DisabledRef = {current: ((disabled: boolean,) => void) | null}
// In the footer component (dom.tsx):
useEffect(() => {
disabledRef.current = setDisabled
return () => {
disabledRef.current = null
}
}, [],)
// In the list component — synced via useEffect on the controlling state:
useEffect(() => {
disabledRef?.current?.(showAddForm,)
if (showAddForm) { /* scroll to bottom */ }
}, [showAddForm,],)
Scroll to bottom on add¶
After showAddForm flips true, walk up from the list’s root ref to find the nearest scrollable ancestor and scroll it smoothly:
const el = rootRef.current
let parent = el?.parentElement
while (parent) {
if (parent.scrollHeight > parent.clientHeight) {
parent.scrollTo({top: parent.scrollHeight, behavior: 'smooth',},)
break
}
parent = parent.parentElement
}
Advanced settings¶
The global advancedMode setting gates:
Additional tabs in
ConfigOverlay(markedadvanced: trueon the tab definition — a section header is injected automatically before the first advanced tab).Advanced field sections inside individual tabs (wrap in
{advancedMode && (...)}).
File Conventions¶
File |
Contains |
|---|---|
|
Tab definitions, |
|
Purely presentational React. Accepts refs, emits callbacks. No direct wiki reads/writes except on initial load. |
|
Scoped styles for that component only. No global selectors. |
Each new list tab needs at minimum: an edit list component, a sort list component, and corresponding CSS modules. The sort component uses @dnd-kit/core + @dnd-kit/sortable and follows the pattern in RemovalReasonSortList.tsx.