Skip to content

Form builder & share UI: Figma system-flat polish pass#112

Merged
Vijayabaskar56 merged 16 commits into
mainfrom
dev
Jun 16, 2026
Merged

Form builder & share UI: Figma system-flat polish pass#112
Vijayabaskar56 merged 16 commits into
mainfrom
dev

Conversation

@Vijayabaskar56

Copy link
Copy Markdown
Member

Summary

A batch of form-builder and share/embed UI work matching the latest Figma system-flat designs, plus a couple of correctness/a11y fixes.

Form builder — buttons & editor

  • Button nodes are now inline-editable (native input on the pill), dropping the old gear + popover.
  • Buttons join the Tab order; Tab traversal refactored onto shared find{Next,Prev}FocusTarget / goToFocusTarget helpers.
  • Fix: don't trap keyboard focus on the final/first button — native Tab carries focus out when there's no in-editor target (WCAG 2.1.2).

Forms — branding & metrics

  • Inline "Made with Reform." branding beside Submit; popup uses a full-width footer bar (Figma 26075-12633 / 25778-10461).
  • Input fill height 28px → 30px across field renderers, skeleton, node, and the --bf-input-height fallback.
  • Matrix/linear-scale labels + value tiles at 14px; scale step is now a typeable number input (clamped to bounds); "Add Anchor" → "Scale Anchor" with the Figma glyph.

Editor chrome / share

  • Block gutter: drop tooltips on delete/add/drag; block menu locks settled side and align so view-morphs don't flip the popup.
  • Dropdown item focus/open tint accent → secondary.
  • Share tabs match Figma system-flat; Copy Link / Get Code weight + border fixes; sidebar + header system-flat pass; switch/divider fixes.

Test plan

  • pnpm exec tsc --noEmit — clean.
  • Pre-commit (fmt/lint/knip) and pre-push (typecheck/test) hooks pass.
  • Manual: Tab through a multi-step form's fields and buttons; confirm focus exits the editor at the final Submit; edit button labels inline; type/clamp scale step.

- ghost-flat button variant (ghost minus the base 1px transparent border)
- header icon buttons unified via HEADER_ICON_BUTTON_CLS (28×28, 8px radius, gray-800)
- SidebarSection collapsible="flat": flat header + hover-only caret, click to collapse
- elastic-slider revealOnHover: flat at rest, gray track + chrome reveal on hover/drag/focus
- Auto handle reads as solid gray/300 sliver (color carries "faint", not opacity)
- block-gutter Block{Add,Delete,Drag} + ImageLine icons pulled verbatim from Figma
- right sidebar default width 340→304
…ythm)

Figma divider frame (node 25424-12791) is 8px tall with the hairline centered —
4px above/below the line — flanked by 16px section gaps, so the line lands 20px
from section content and 20px from the next header. Was 16/16 (pb-4 + gap-4),
causing the divider to sit too tight. Bump FlatCollapsibleSection pb-4→pb-5 and
the customize sections wrapper gap-4→gap-5. Verified live: 20px/20px around every
divider, 16px header→rows, 28px rows, 8px row gaps across all 6 sections.
Unchecked hover/active were wired to --popover-foreground (gray-800) and
--card-foreground (gray-900), so hovering an "off" switch turned its track
near-black — reading as toggled-on. The checked side only does a subtle
primary/86 darken; the unchecked side should likewise stay neutral. Keep
bg-accent through hover/active so colour only changes on a real toggle.
The Copy Link / Get Code footer pair read as "big/small" — same root as the
header Share/Publish case. Their boxes were already identical (both size="sm" =
h-7, same px-2.5/ps-1.5 padding, text-[14px] font-medium, 16px icon), so the
mismatch wasn't the box: Copy Link was variant="ghost" (transparent, no fill or
shadow) next to the solid dark default Get Code, so it read as lighter/smaller.
Switch it to variant="secondary" — a solid gray-100 pill — so the two sit as an
equal-weight matched pair (matches the gray pill in the Figma/screenshot). Drop
the now-redundant text-foreground (secondary-foreground already = foreground).
The box mismatch was the border, not the fill: ghost keeps a 1px transparent
border (border:1 in devtools) while the default primary Get Code is border-none.
Switch Copy Link to the existing ghost-flat variant (ghost minus the border) so
the two boxes line up, keeping the light ghost look. Reverts the prior
secondary-pill attempt.
…ping

In a themed form, the inner country button + text input carry
[data-bf-input-fill], so `.bf-themed [data-bf-input-fill]` sets their height to
--bf-input-height (the customize-sidebar "Input height"). The wrapper, though,
used a fixed [&]:h-7/8/9 + overflow-hidden — so any customized input height made
the inner pieces (e.g. 44px) overflow the 28px wrapper and the phone number /
placeholder clipped at the bottom. Switch the wrapper to [&]:min-h-* so it hugs
the inner pieces: it grows with --bf-input-height (verified 44px in, 44px out)
and stays 28/32/36 on Auto/non-themed. Other inputs already adapt because they
ARE the data-bf-input-fill element; phone has a wrapper that wasn't tracking it.
- Embed: drop Question layout, move Show progress bar into Appearance,
  Height row below Dynamic height, static EmbedStandardPreview mockup,
  + Add Domain / domain dropdown, 14px Figma alert-circle info icon
- Popup: rename first section to Flow, restore Position select,
  remove mockup host-skeleton lines
- Full Page: split Appearance/Preferences, static EmbedFullPagePreview
- Hide Track-analytics info icon on all share tabs (Figma)
- version-history: rail/line/row colors + spacing, header to gray-800
- customize/settings/share headers unified to 14px/medium/gray-800
… 25778-10461)

Render the branding footer inline on the final Submit row — "Made with " (Inter
gray/500) + the Timeless Serif italic "Reform." wordmark — when settings.branding
is on. Threaded branding through StepForm → the submit/grouped/auto-button rows;
the form now owns the badge so it shows in BOTH the editor preview and the live
public form (both render via form-preview-from-plate).

- Single source: removed preview-mode's 3 old centered "Made with ✨ Reform" bars
  (+ pb-16 spacer, BrandingBadge, now-unused imports) to avoid double-badging; the
  form's inline badge supersedes them everywhere.
- previewSettings.branding now folds in the share-sidebar embedBranding toggle so
  the preview badge tracks the toggle live.
- Inline fontSize:14px (the form's base size out-races Tailwind utilities, same
  reason the submit button pins fontSize inline).
- pb-7 (28px) on the form so the final row isn't flush against the bottom edge.

Verified live: single visible badge, gray/500 #999, 14px, Timeless Serif italic
wordmark, inline with Submit, 28px below.
The popup gets a distinct branding treatment from the embed/full-page inline
badge: a full-width footer BAR pinned at the popup's bottom edge — gray/100 fill,
centered "Made with " (gray/600 Inter) + Timeless Serif italic "Reform." wordmark,
13px vertical padding (42px tall).

- New PopupBrandingBar; rendered at the bottom of LinearLayout + FieldByFieldLayout
  when isPopup && settings.branding (single source → preview popup + live popup
  iframe both render via form-preview-from-plate).
- Suppress the inline submit-row badge in popup (branding && !isPopup) so the two
  treatments never both show.
- Inline fontSize:14px (form base size out-races utilities, per FormBrandingBadge).

NOTE: live popup not visually verified this session — dev server returned 500 on
all form routes (transient loader/DB flakiness). Typecheck green; markup mirrors
the already-verified inline badge. Verify the popup render when the dev server recovers.
The trash, plus, and drag-dot gutter icons are self-explanatory; the hover
tooltips (esp. "Drag to move, Click to open menu") add noise. Unwrap all three
from Tooltip and keep accessible names via aria-label (Add block below + the
drag/settings handle previously had none) so screen-reader semantics are intact.
Drop the now-unused Tooltip import.
…t at the edge

The menu locks Base UI's collision-settled placement one frame after open (so
inline-panel resizes can't re-flip it around the anchor). But it only read/locked
data-side; align stayed hard-coded "end". The drag handle is in the left gutter,
so collision flips align end→start to fit — then the lock reverted it to "end"
and the popup clipped off the left edge. Read data-align and lock it symmetrically
with side: now it opens to the right when there's no room on the left, mirroring
the already-working top/bottom flip.
In this theme --text-sm = 0.8125rem (13px), so the matrix row labels and the
linear-scale value tiles rendered at 13px. Bump them to text-[14px] across both
the editor nodes and the live form fields so editor and live stay consistent:
- form-matrix-node (editor row label) + MatrixField (live row header)
- form-linear-scale-node (editor tile) + LinearScaleField (live tile)
Column headers stay text-xs (12px) — those weren't the 13px the report flagged.
The submit/next/previous button label is now edited in place via a native
auto-sizing input (field-sizing-content) inside the contentEditable=false pill —
the same pattern the matrix/linear-scale labels use, so Slate ignores it. It
writes element.label directly (the property the preview transform reads first:
label || childText || buttonText), so the live form is unchanged. Clicking
anywhere on the button focuses the label; Enter commits (blurs); pointer/keys
stopPropagation so the editor doesn't hijack them. Removes the hover gear icon,
its Popover dialog, and the now-unused Button/Input/Label/Popover/Settings imports.
Now that button labels are inline-editable, Tab includes them. Added a unified
focus-target traversal (findNext/PrevFocusTarget + goToFocusTarget) where buttons
are stops (their native label input) alongside field Slate carets:

- Tab from the last field lands on the page's button(s) before crossing pages.
- Buttons traverse in document order — Previous → action (Submit/Next).
- From the action button: cross to the next page's first field; if it's the final
  Submit with no page after, Tab stays (button is the last stop). Trailing empty
  paragraphs after Submit are not stops. A following thank-you page is reachable.
- Shift+Tab mirrors it.

Wired into the NavigationPlugin, the button input's own keydown (it stopsPropagation
so the plugin can't see it), and the matrix / option-item / logic-block edge
handoffs so every block hands off to buttons consistently. The selection-redirect
overrides (which keep the Slate caret off buttons) are unchanged — buttons are
reached by focusing their input, not by Slate selection.
… 2.1.2)

The Tab handler called preventDefault() unconditionally, then only moved focus
when a target existed. So Tab forward off the final Submit (no page after) — or
Shift+Tab off a button with nothing focusable before it — suppressed the default
Tab but moved nothing, stranding focus on the input. Only preventDefault when we
actually navigate internally; otherwise let native Tab carry focus out to the
surrounding UI. Verified live: forward off final Submit no longer prevents default
(focus escapes); Shift+Tab still lands on the previous field.
…con, dropdown focus tint

- Input fill height 28px → 30px across field renderers, skeleton, node + the
  --bf-input-height fallback (styles.css comment kept in sync).
- Scale step: replace the static value tile with a typeable number input
  (draft state, clamp to [stepMin, stepMax] on commit/blur).
- Rename 'Add Anchor' → 'Scale Anchor' with the Figma ScaleAnchorIcon glyph.
- Dropdown items: focus/open tint accent → secondary.
- Scale range/step labels: tracking + gray-700 per Figma.
@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
reform Ready Ready Preview, Comment Jun 16, 2026 1:54pm

Request Review

@Vijayabaskar56 Vijayabaskar56 merged commit 0715ed1 into main Jun 16, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant