# ChargeForms Builder — Design Guidelines

**Status: superseded.** ChargeForms now deliberately mirrors Fluent
Forms' admin/builder UI conventions (layout, information architecture,
spacing density, control patterns, color temperature). Fluent Forms'
public UI is the visual reference standard for this plugin. The values
below were the prior, now-retired violet-based standard — kept here for
history, not as the current source of truth. Do not add new components
against this document; consult the Fluent Forms reference screenshots
(`docs/reference/fluent-forms/`) and the current token values in
`assets/css/admin.css` / `assets/src/builder/shared/styles/tokens.css`
instead.

Source of truth for every token: `assets/src/builder/shared/styles/tokens.css`
(builder-local, prefixed `--fcb-*`) layered on top of `assets/css/admin.css`'s
shared plugin-wide scale (`--fc-*`). Builder components should reach for
`--fcb-*` first when one exists for the job (shadows, card radius, ink,
soft borders); fall back to the shared `--fc-*` scale for color, spacing,
and typography where no builder-specific override exists.

---

## 1. Typography scale

```
11px  — micro labels, badge text (SECTION, Primary)
12px  — settings-row field labels, palette category tabs
12.5–13.5px — body copy inside panels: inputs, condition rows, quick-add items
14px  — field preview labels, base panel body text
15px  — palette/settings h3 headings
16px  — panel titles ("Field Settings"), settings-section headers (13.5px for
        the section header itself, 16px for the panel-level h4)
17–19px — modal headings, canvas-empty title
```

Weight discipline:
- `500` — secondary/de-emphasized labels
- `600` — field labels, palette item names, settings-row labels, section headers
- `700` — panel titles, modal headings, badges, canvas-empty title

Letter-spacing: `-0.01em` on anything 15px+ (headings read tighter and more
confident at that size); `0.04–0.08em` only on true uppercase micro-labels
(badges, kickers) — never on a heading-weight element.

## 2. Spacing rhythm

This is the single biggest lever for "premium vs. admin panel." Don't
default to compact — err toward more air, then pull back only if it
looks empty, not the reverse.

```
Panel padding (canvas / palette / settings):  28px
Builder grid gutter (between the 3 panels):   28px
Canvas field card padding:                    22px 24px 22px 22px
Palette item padding:                         13px
Settings-row vertical rhythm:                 18px between rows, 7px label→input
Input padding (text/number/select/textarea):  11px 14px (settings-row),
                                               9px 12px (settings-row select)
Modal padding:                                32px
Popover padding (Quick Add / History):        14–16px
```

## 3. Radius scale

```
--fcb-radius-control: 8px   — inputs, selects, small buttons, badges (non-pill)
--fcb-radius-card:    14px  — the 3 main panels (canvas/palette/settings)
14px                        — popovers (Quick Add, History)
16px                        — Preview page chrome frame + sidebar
18px                        — modal
--fc-radius-full             — pills (palette tabs, SECTION badge, Primary badge)
--fc-radius-circle            — perfect circles only (icon-button-in-a-circle
                                 contexts) — do NOT use for pill-shaped badges,
                                 that's --fc-radius-full's job (see Slice 13's
                                 fix for exactly this mix-up)
```

## 4. Shadow scale

Every shadow in this system is **layered** — a tight low-opacity contact
shadow plus a soft wide ambient one — never a single flat
`box-shadow: 0 Npx Mpx rgba(...)`. That's what makes a card read as
lifted off the page instead of just outlined.

```css
--fcb-shadow-card:       0 1px 2px rgba(15,23,42,.04), 0 8px 20px -6px rgba(15,23,42,.10);
--fcb-shadow-card-hover: 0 2px 4px rgba(15,23,42,.05), 0 14px 32px -8px rgba(15,23,42,.14);
--fcb-shadow-panel:      0 1px 2px rgba(15,23,42,.03), 0 20px 48px -16px rgba(15,23,42,.10);
```

- **Resting cards** (canvas field, palette item at rest) → `--fcb-shadow-card`
  or none, depending on density (palette items are borderless at rest,
  shadow appears on hover only — see §7).
- **Hover / active card** → `--fcb-shadow-card-hover`.
- **Main panels** (canvas/palette/settings) → `--fcb-shadow-panel`.
- **Popovers** (Quick Add, History) → `0 4px 12px rgba(15,23,42,.06), 0 24px 48px -12px rgba(15,23,42,.20)`.
- **Modal** → `0 4px 12px rgba(15,23,42,.06), 0 32px 64px -12px rgba(15,23,42,.28)`.
- **Floating (dark) toolbar** → its own richer shadow with an inset highlight:
  `0 4px 12px rgba(15,23,42,.16), 0 16px 40px -8px rgba(15,23,42,.35), 0 0 0 1px rgba(255,255,255,.06) inset`.

Rule of thumb: the higher an element floats above the page (popover >
modal > card), the wider and softer its ambient shadow should spread —
elevation is communicated by shadow *spread*, not just opacity.

## 5. Color system

Unchanged from `DESIGN_SYSTEM.md` §2 — this pass didn't touch the color
palette, only how borders/shadows use it. Two new builder-local tokens:

```
--fcb-ink:         #16181d   — headings/titles specifically (slightly
                                deeper than the shared --fc-ink, used
                                anywhere a heading needs to read as the
                                most confident text on the panel)
--fcb-border-soft: rgba(15,23,42,.08)  — the default card/input/panel
                                border going forward. Replaces flat
                                --fc-border (#dadbdd) wherever a border's
                                job is just "a soft edge," not "a hard
                                divider." Use plain --fc-border only for
                                genuine dividers (row separators, table
                                rules) where you want it to actually read
                                as a line.
```

## 6. Button variants

- **Primary** (`.button-primary` in `.fc-topbar-actions`) — accent gradient
  fill, white text, lifts on hover (`translateY(-1px)` + deeper shadow).
  One per screen, for the single most important action (Save Form).
- **Secondary** (`.fc-canvas-empty-btn.is-secondary`, `.fc-icon-btn`) —
  `--fcb-border-soft` border, `--fc-surface` background, no shadow at
  rest, subtle accent-soft fill on hover.
- **Ghost / dashed-add** (`.fc-attr-add`) — dashed accent border, no fill,
  used for "add a row" actions inside a list (custom attributes, logic
  conditions/groups/actions). One canonical implementation — do not
  re-author this a third time; extend `.fc-attr-add`.
- **Icon button** (`.fc-icon-btn`, `.fc-floating-toolbar button`) — square,
  `--fcb-radius-control` (light context) or 8px (dark floating toolbar),
  34px light / 32px dark. Always needs a `:focus-visible` ring — see §14.

## 7. Input variants

One shared rule per field type, applied wherever it appears (settings
panel, Rule Builder, Custom Attributes, modal) — do not hand-roll a new
input style per screen.

- **Text / number / textarea**: `--fc-bg` background at rest (a visible
  step down from the white panel), swaps to `--fc-surface` (white) on
  focus — this "lights up" feedback matters more than the border color
  change alone. `--fcb-border-soft` border, `--fcb-radius-control` radius,
  `11px 14px` padding.
- **Select**: same treatment, no forced `width:100%` (selects can sit
  several-in-a-row, e.g. Math Field's operand pickers).
- **Toggle** (boolean settings — Required/Locked/Hidden/Read Only/Disabled,
  Enable Conditional Logic, Rule Enabled): a real switch
  (`.fc-settings-row-inline input[type="checkbox"]`, styled via
  `appearance:none` + `::before`), never a plain checkbox for a genuine
  on/off setting. Track: 38×22px, pill radius, `--fc-border` → `--fc-accent`
  on check. Thumb: 18px circle, white, soft shadow, slides via `transform`.
- **Small inline checkbox** (a literal multi-select list, not a toggle —
  e.g. a "NOT" modifier next to a compact button group): plain
  `accent-color` checkbox is fine when a full switch would be visually
  oversized for the density of its context. Don't force every checkbox
  into a switch — only genuine standalone boolean *settings*.

## 8. Card variants

- **Canvas field card** (`.fc-canvas-field`) — the primary content unit.
  `--fcb-radius-control` radius, `--fcb-shadow-card` at rest, lifts to
  `--fcb-shadow-card-hover` + `translateY(-1px)` on hover, solid accent
  left-edge + `--fc-shadow-selection` ring when active.
- **Palette item card** (`.fc-palette-item`) — borderless/shadowless at
  rest (a denser list, not individually "lifted" cards), gains
  `--fcb-shadow-card` + border + lift only on hover. Don't give it a
  resting shadow — that reads as visual noise at 2-per-row density.
- **Rule Builder group card** (`.fc-logic-group`) — same treatment as the
  canvas field card (`--fcb-shadow-card` at rest), since a condition
  group is conceptually the same "unit of content" even though it lives
  in a side panel, not the canvas.

## 9. Modal guidelines

One canonical modal (`.fc-modal`/`.fc-modal-overlay`) for the whole
builder — do not introduce a second modal pattern.

- 18px radius, 32px padding, `0 4px 12px rgba(15,23,42,.06), 0 32px 64px -12px rgba(15,23,42,.28)` shadow.
- Heading: 19px/700, `--fcb-ink`.
- Always animates in (`fc-quick-add-in` keyframe — reuse it, don't write
  a near-identical one).
- Always dismissible three ways: backdrop click, Escape, explicit close
  button — plus Cancel if the modal has a destructive/committing action.
- `z-index: 100000` is a deliberate literal, not the documented
  `--fcb-z-modal` token — `.fc-app.is-fullscreen` also hardcodes 100000
  and the modal must render above it. Don't "fix" this to the token
  without also reconciling the fullscreen z-index; they're coupled.

## 10. Toolbar guidelines

Three toolbar contexts exist, each intentionally distinct — do not force
them to look identical, but each internally must be consistent:

- **Top toolbar** (`.fc-topbar`) — light, bordered icon buttons, 34px,
  `14px 22px` padding, `--fcb-border-soft` bottom border.
- **Per-field corner toolbar** (`.fc-canvas-field-toolbar`) — floats
  *above* the card edge (`top:-14px`) with its own elevation
  (`--fcb-shadow-card`), not embedded flush in the corner. Hover-revealed
  only.
- **Floating contextual toolbar** (`.fc-floating-toolbar`) — the one
  deliberately dark surface in the builder (`#14161f`), Figma/Notion-style
  "selection toolbar." 12px radius, richer shadow with an inset highlight
  (see §4). This is an intentional exception to the light-surface rule
  everywhere else — don't lighten it to "match."

## 11. Empty state guidelines

One canonical shape: icon chip (64×64px, 18px radius, `--fc-accent-soft`
background) → bold 18px title → 13.5px muted hint (max-width 340px,
1.6 line-height) → optional button row. Generous padding (80px vertical
on the canvas empty state — this is a resting/first-impression screen,
it can afford to breathe more than a working panel). Dashed border
(`1.5px dashed var(--fc-primary-100)`) only for a genuine drop-target
empty state (the canvas); a settings-panel empty state doesn't need a
border at all.

## 12. Icon sizing

Dashicons remain the icon system for this pass (full replacement is
tracked as separate, larger scope — see `DESIGN_SYSTEM.md` §9). Sizing
by context:

```
13px  — inline field-flag icons (locked/hidden markers), pane/column controls
14px  — drag handles (canvas AND logic-rule — keep these matched), section-icon
15–16px — icon-button glyphs (topbar, device toggle)
18px  — palette item icons
```

Never mix Dashicons-default (~20px, unset) with a sized icon in the same
row — always give an icon an explicit `width`/`height`/`font-size` triplet.

## 13. Animation durations

```
--fc-motion-fast (100ms) / --fcb-motion-fast (120ms) — hover/press feedback,
    should read as instant, not animated
--fc-motion-base (180ms) / --fcb-motion-base (180ms) — the default: popover
    entrances, section expand/collapse, toggle slide
--fc-motion-slow (260ms) — reserved for a large visual distance (panel
    slide, not currently used in the builder proper)
```

One curve everywhere: `var(--fc-ease)` (`cubic-bezier(0.16,1,0.3,1)`), a
gentle ease-out. Never plain `ease`/`linear` for a new rule — if you find
yourself writing `transition: color 0.15s ease`, stop and use the token
instead.

Every new animation must be added to the `prefers-reduced-motion` block
at the bottom of `style.css` — collapse duration near-zero, don't remove
the rule outright (state must still change correctly, just without
motion). Exception: a spinner (`.fc-spinner`) — slow it under reduced
motion, don't stop it; it conveys real state, not decoration.

## 14. Hover states

Every interactive element needs one. No exceptions — "no hover/focus
state" was one of the concrete bugs Slice 13 found and fixed across a
dozen components. The pattern:

- Cards: background tint (`--fc-primary-50`) + border color shift +
  shadow escalation + `translateY(-1px)` (canvas field) OR just
  border+shadow, no lift, for denser lists (palette items).
- Buttons/icon buttons: background tint, color shift to `--fc-accent-dark`.
- Destructive actions (delete buttons): tint toward `--fc-danger`, not
  the accent blue.

## 15. Focus states

Every focusable control needs a visible `:focus-visible` state — never
rely on the browser default outline, and never suppress the outline
(`outline:none`) without a replacement in the same rule.

- **Inputs/selects/textareas**: border → `--fc-accent`, background → white
  if it wasn't already, `box-shadow: 0 0 0 4px var(--fc-accent-soft)` (a
  4px ring in this pass's inputs; 3px is the older, still-valid
  convention on smaller/denser controls like Rule Builder condition
  rows — both are acceptable, pick 4px for primary panel inputs, 2–3px
  for compact inline controls).
- **Buttons/icon buttons**: `box-shadow: 0 0 0 2–3px var(--fc-primary-50)`,
  no border color change needed.
- **Toggles**: same ring, applied to the switch track itself.
- A wrapper-level `:focus-within` ring (not a ring on the bare input) is
  correct specifically when the input can't carry its own visible border
  (search boxes, the Rich Text raw-HTML textarea) — see
  `.fc-richtext-editor:focus-within` for the reference implementation.

---

## What this document is not

Not a component library with copy-pasteable JSX — it's the values and
rules a component must follow. Not a retroactive mandate to rewrite
every screen outside the builder (Forms list, Entries, Reports, etc. —
those follow `assets/css/admin.css`'s own scale, tracked separately in
`DESIGN_SYSTEM.md`). Not exhaustive for every possible future component —
when something genuinely new comes up, extend this document with the
same reasoning shown above (why this value, not just what it is), then
build against it.
