Add native <dialog> component (.dialog parts, sizes), SynthJS showModal/close on existing open/dismiss, confirm pattern
Specification
Motivation
SynthCSS has no documented dialog component. This adds a small, predictable vocabulary for:
- record editing;
- detail views;
- explicit confirmations.
AI agents should be able to generate it consistently. It is built on native <dialog>, with optional SynthJS wiring.
Scope
CSS
- Classes.
.dialogis applied to<dialog>. The parts are.dialog-header,.dialog-bodyand.dialog-footer. - Size variants. Small, default and large.
- Follow the repo's existing modifier naming convention, e.g.
.dialog-sm/.dialog-lg. Inspect the existing components and match them.
- Follow the repo's existing modifier naming convention, e.g.
- Backdrop. Style
::backdropwith existing tokens.- Add new tokens only if none fits.
- Document any new tokens.
- Layout.
- Width and height are capped to the viewport (e.g.
max-height: calc(100dvh - margin)). - There are side margins on narrow screens.
.dialog-bodyscrolls (overflow: auto).- Header and footer stay visible, so actions remain reachable.
- Width and height are capped to the viewport (e.g.
- Closed state. No
displayrule targets.dialog:not([open]). Any componentdisplayrule is scoped to.dialog[open], so the native closed state staysdisplay: none. - Motion. Transitions are optional. If added, disable them under
prefers-reduced-motion: reduce. - Focus. Preserve visible focus styles on controls inside dialogs.
- Cascade. Place the styles in the project's existing layer and file structure.
SynthJS (optional runtime)
Extend the existing data-synth-open / data-synth-dismiss handling. Do not add new attributes, attribute values or modifiers.
- Opening. If the resolved open target is a
<dialog>element, callshowModal().- If it is already open, do nothing and do not throw.
- Record the trigger element for focus return.
- Dismissing. If the resolved dismiss target is a
<dialog>, callclose().- If it is already closed, do nothing and do not throw.
- Other targets. Non-dialog targets behave exactly as before.
- Escape. Use the native
cancelbehaviour. Do not add a custom key handler that closes dialogs. - Initial focus.
- If an element inside has
autofocus, rely on native behaviour. - Otherwise, focus the first focusable control that is enabled and visible.
- If an element inside has
- Focus return. On the dialog's
closeevent, return focus to the recorded trigger if it is still connected, visible and not disabled. - Containment. Rely on native modal inertness. Do not write a custom focus trap.
- Missing or invalid targets. Do nothing silently, with no uncaught error, and do not break other handlers.
- Backdrop clicks. No backdrop-click dismissal.
Confirmation pattern
Confirmations are a documented markup pattern only. No JS API executes actions.
- Examples include an explicit Cancel button and a clearly labelled Confirm button.
- In the destructive example, Cancel has
autofocus. - Docs show application code handling confirmation, via either:
- a click listener on Confirm; or
<form method="dialog">with button values, then thecloseevent andreturnValue.
Accessibility
- Use native dialog semantics with no redundant
role. - Every dialog has
aria-labelledbyoraria-label. aria-describedbyis documented as optional.- All examples are fully keyboard-operable.
Docs and AI contract
- Update
synthcss.llm.mdandsynthcss.ai.jsontogether, plus any versioned sync checks or fixtures. - Version. Bump to the next minor contract version. Determine the current version from the repo and follow the documented version policy.
- Compatibility. Existing consumers must keep working.
- Contents to document:
- classes, parts and variants;
- required ARIA;
- triggers;
- focus behaviour;
- the boundary between CSS and runtime behaviour.
- Progressive enhancement. The CSS works alone. Include a native JS snippet using
showModal()/close()without SynthJS. - SynthMCP. The existing contract-based tools must surface the dialog entries. Add no new tool.
- Assumption: this works via contract data alone. Change MCP code only if discovery is hard-coded.
Showcase
Add three working examples:
- an edit-record form dialog;
- a detail dialog with long scrolling content;
- a destructive confirmation.
Each must render correctly at a 360px viewport.
Test harness
Inspect the repo for an existing automated browser test harness and reuse it if present.
If none exists:
- add a minimal dev-only Playwright setup (Chromium only);
- add an npm script to run it;
- integrate it into the CI workflow.
Acceptance criteria
- Open and close. For each showcase dialog, verified in a real browser:
- clicking its trigger opens it via SynthJS, with
openset and:modalmatching; - it closes via its Cancel/close control;
- it closes via Escape;
- after close, focus is on its trigger.
- clicking its trigger opens it via SynthJS, with
- Containment. While a dialog is open, repeated Tab and Shift+Tab never focus an element outside it.
- Names and focus.
- Every showcase dialog has a non-empty accessible name.
- Buttons show visible focus styles.
- In the destructive example,
document.activeElementon open is the Cancel button.
- Visibility and narrow screens.
- A closed
.dialogof every size variant has computeddisplay: none. - At a 360px viewport, no open variant causes horizontal document overflow.
- Long
.dialog-bodycontent hasscrollHeight > clientHeightand is scrollable, while the footer stays visible.
- A closed
- Regressions. Existing open, dismiss, toggle, tabs and dropdown behaviours pass their existing tests. Add regression tests where none exist, including the shared open/dismiss handler on non-dialog targets.
- No uncaught errors. None are thrown by:
- opening a missing target;
- opening an already-open dialog;
- dismissing an already-closed dialog. Check this via a page error listener in tests.
- Test coverage and CI.
- Automated browser tests cover criteria 1–6 and run in CI.
- Build, contract sync and all existing CI checks pass.
- Contract files.
synthcss.llm.mdandsynthcss.ai.jsondescribe the same dialog API.- The contract version is bumped to the next minor per policy.
- Existing SynthMCP lookup or search returns the dialog entries, with a test if the MCP package has tests.
- Docs. Docs cover usage with and without SynthJS, and how to handle confirmed actions.
Out of scope
- Custom focus traps.
- Dialog polyfills.
- Drawers or sheets.
- Nested or stacked modals.
- Promise-based confirm helpers.
- Arbitrary action execution.
- Backdrop-click dismissal.
- New runtime dependencies. A dev-only Playwright is allowed only if no harness exists.
Attachments
Discussion · 0
No comments yet.