Config Popup Style Guide¶
Reference for building tabs and components within the Toolbox config overlay (ConfigOverlay / WindowTabs).
Design Tokens¶
All values come from the --toolbox-* tokens in extension/data/css/base.css; stylelint rejects hex and named colors, literal radii, and literal drop shadows (see the Coding Style Guide). Use raw rgb() only for a value no token covers, with an /* intentional: */ comment. Several of the recurring patterns below are also available as shared classes in extension/data/css/Base.module.css (cardtitle, untitled, draghandle, inlineiconbutton, cardactions, rowcard, warning, secondaryitalic, columngaplarge, …), pulled in with composes: x from "...".
Light-mode values are listed for reference; every color token also has a dark-mode value.
Token |
Light 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; placeholder text |
|
|
Non-primary |
|
|
Card body background; non-primary button; action grid |
|
|
Non-primary button text |
|
|
Card / section borders |
|
|
Card header strip; “add” card background; selected item |
|
|
Context badge fill |
|
|
Card titles, field labels, preview text |
|
|
Section headers, hints, badge text |
|
|
Primary button; active icon button; expand-toggle link |
|
|
Primary button hover |
|
|
Primary button pressed |
|
|
Primary button disabled fill |
|
|
Warning banner background |
|
|
Warning banner border |
|
|
Warning banner left accent |
|
|
Warning banner text |
|
|
Validation / error messages |
|
|
Icon button hover tint |
|
|
Shadow color used by |
Typography¶
Role |
Size |
Weight |
Color |
Notes |
|---|---|---|---|---|
Section title |
|
bold |
|
Uppercase, |
Field label |
|
bold |
|
— |
Sub-group label |
|
bold |
|
Uppercase, |
Card title |
|
bold |
|
— |
Body / preview |
|
normal |
|
|
Hint / description |
|
normal |
|
Italic |
Empty state |
|
normal |
|
Italic |
Badge |
|
bold |
|
|
Link / toggle |
|
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¶
The snippets below follow RemovalReasonList.module.css, ModMacroList.module.css, and RemovalSettingsTab.module.css, with shared Base.module.css classes expanded inline.
Card List¶
A vertically stacked list of items, used for editable lists that also support drag reordering.
.root {
display: flex;
flex-direction: column;
gap: var(--toolbox-sp-8);
}
.cardList {
display: flex;
flex-direction: column;
gap: var(--toolbox-sp-6);
margin-top: var(--toolbox-sp-4);
}
Card (Editable)¶
Two zones: a header strip and a collapsible body.
.card {
border: 1px solid var(--toolbox-surface-border);
border-radius: var(--toolbox-radius-md);
background: var(--toolbox-action-bg);
overflow: hidden;
}
/* "new item" variant */
.addCard {
background: var(--toolbox-selected-bg);
border-color: var(--toolbox-input-focus);
}
.cardHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--toolbox-sp-6) var(--toolbox-sp-8);
background: var(--toolbox-selected-bg);
border-bottom: 1px solid var(--toolbox-surface-border);
gap: var(--toolbox-sp-8);
}
.cardTitle {
font-size: 1em;
font-weight: bold;
color: var(--toolbox-text-body);
flex: 1;
min-width: 0; /* enables text-overflow inside flex */
}
.untitled {
color: var(--toolbox-input-focus);
font-weight: normal;
}
Card header slot order: drag handle → title (flex: 1) → badges → actions
Sorting¶
Each card header carries a drag handle (draghandle in Base.module.css: cursor: grab / cursor: grabbing, color --toolbox-input-focus, the dragHandle icon at 18px), and cards can be dragged in either view. The list uses @dnd-kit/core + @dnd-kit/sortable. A <SortToggleButton> in the tab footer (shared/controls/SortToggleButton.tsx) switches the list to a compact sort view that collapses each card to its header; the list reads that state with useSortMode(sortRef). Entering sort mode closes any open editor, and leaving it saves the new order (a pending order is also saved on unmount).
For a flat single-row card without a header strip, compose rowcard from Base.module.css.
Context Badges¶
Shown in the card header to indicate which contexts an item applies to.
.contextBadges {
display: flex;
gap: var(--toolbox-sp-3);
flex-shrink: 0;
}
.contextBadge {
font-size: var(--toolbox-fs-10);
font-weight: bold;
color: var(--toolbox-text-secondary);
background: var(--toolbox-badge-bg);
border-radius: var(--toolbox-radius-sm);
padding: var(--toolbox-sp-1) var(--toolbox-sp-4);
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: var(--toolbox-sp-6) var(--toolbox-sp-8);
}
.previewClamped {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
font-size: 1em;
color: var(--toolbox-text-body);
line-height: 1.4;
}
.previewFull {
font-size: 1em;
color: var(--toolbox-text-body);
line-height: 1.4;
}
.expandToggle {
font-size: var(--toolbox-fs-11);
color: var(--toolbox-accent-color); /* underline on hover */
}
.noText {
font-size: var(--toolbox-fs-11);
color: var(--toolbox-input-focus);
font-style: italic;
padding: var(--toolbox-sp-6) var(--toolbox-sp-8);
}
Edit Form (inside a card)¶
.editForm {
display: flex;
flex-direction: column;
gap: var(--toolbox-sp-10);
padding: var(--toolbox-sp-8);
}
.editField {
display: flex;
flex-direction: column;
gap: var(--toolbox-sp-3);
}
/* 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: var(--toolbox-fs-11);
font-weight: bold;
color: var(--toolbox-text-body);
}
.editButtons {
display: flex;
gap: var(--toolbox-sp-6);
}
Always use <TextInput> (from NormalInput.tsx), <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: var(--toolbox-sp-8);
padding: var(--toolbox-sp-8);
border: 1px solid var(--toolbox-surface-border);
border-radius: var(--toolbox-radius-sm);
background: var(--toolbox-action-bg);
}
.actionGroup {
display: flex;
flex-direction: column;
gap: var(--toolbox-sp-5);
}
.actionGroupLabel {
font-size: var(--toolbox-fs-10);
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--toolbox-text-secondary);
margin-bottom: var(--toolbox-sp-2);
padding-bottom: var(--toolbox-sp-3);
border-bottom: 1px solid var(--toolbox-surface-border);
}
Section Grouping (flat-form tabs)¶
For tabs that present a flat form rather than a card list.
.section {
margin-bottom: var(--toolbox-sp-18);
}
.section:last-child {
margin-bottom: 0;
}
.sectionTitle {
font-size: var(--toolbox-fs-11);
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--toolbox-text-secondary);
margin-bottom: var(--toolbox-sp-8);
padding-bottom: var(--toolbox-sp-4);
border-bottom: 1px solid var(--toolbox-surface-border);
}
.fieldLabel {
display: block;
font-size: var(--toolbox-fs-11);
font-weight: bold;
color: var(--toolbox-text-body);
margin-bottom: var(--toolbox-sp-3);
margin-top: var(--toolbox-sp-10);
}
.fieldHint {
font-size: var(--toolbox-fs-11);
color: var(--toolbox-text-secondary);
font-style: italic;
}
Radio Option Cards¶
For mutually exclusive settings choices (e.g. reply type in removal reasons).
.radioOption {
padding: var(--toolbox-sp-7) var(--toolbox-sp-10);
border: 1px solid var(--toolbox-input-border);
border-radius: var(--toolbox-radius-sm);
background: var(--toolbox-action-bg);
}
.radioOption.selected {
border-color: var(--toolbox-input-focus);
background: var(--toolbox-selected-bg);
}
.radioLabel {
display: flex;
align-items: center;
gap: var(--toolbox-sp-7);
cursor: pointer;
font-size: 1em;
}
/* Sub-options indented inside the selected card */
.subOptions {
margin-top: var(--toolbox-sp-8);
margin-left: var(--toolbox-sp-22);
padding-top: var(--toolbox-sp-7);
border-top: 1px solid var(--toolbox-surface-border);
}
Interaction Patterns¶
saveRef / addRef¶
Cross-boundary imperative callbacks — a component exposes a function to its parent (dom.tsx) without prop-drilling React state. Both are TriggerRefs; wire them with useTriggerRef from util/ui/hooks, which always calls the latest handler (so a fresh closure each render is fine) and clears the ref on unmount:
useTriggerRef(saveRef, handleSave,)
useTriggerRef(addRef, () => setShowAddForm(true,),)
disabledRef¶
Same pattern, but carries a state setter:
type DisabledRef = {current: ((disabled: boolean,) => void) | null}
// In the footer component (AddNewButton.tsx):
useEffect(() => {
disabledRef.current = setDisabled
return () => {
disabledRef.current = null
}
}, [disabledRef,],)
// 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. The overlay’s |
|
Config-owned components: |
|
Tab content owned by a feature module (e.g. |
|
Scoped styles for that component only. No global selectors. |
Each new list tab needs at minimum an edit list component (drag reordering via @dnd-kit/core + @dnd-kit/sortable, sort view via useSortMode) and its CSS module. Follow the pattern in RemovalReasonList.tsx.