Modals are one of those things that seem straightforward until you start adding real requirements, multiple triggers, loop contexts, auto-open behavior, or repeat open suppression. SN Modal is an Etch component built on the native <dialog> element that handles all of that with just four core props.

Etch has an EtchDialog component in the pattern library, and it’s a fine starting point. However, SN Modal takes a different approach in a few key areas as well as adding some additional functionality. The biggest philosophical difference is where styling lives. EtchDialog handles visual decisions like background color, padding, and border radius through props, which means every instance is configured independently. SN Modal moves all of that into CSS custom properties instead, so your dialogs share a consistent baseline by default and overrides cascade naturally across your site. On top of that, SN Modal adds features EtchDialog doesn’t have: unlimited triggers via event delegation, auto-open via timer or scroll depth, exit-intent detection, and cookie-based repeat open suppression.

How It Compares to EtchDialog

SN Modal EtchDialog
Props 4 core functional props + conditional trigger props 11 props that include a lot of styling options
Styling approach CSS custom properties with empty-variable override pattern Inline styles via props + CSS
Per-instance overrides Class prop, full CSS cascade Change props per instance
Triggers Unlimited via data-dialog-open delegation Single trigger via querySelector on a selector prop
Auto-open Timer and scroll depth Not supported
Exit intent Velocity-gated mouse exit detection Not supported
Repeat Open Suppression Cookie-based suppression Not supported
Programmatic API window.snModal.open(id) / window.snModal.close(id) window.etchElements.etchDialog[id].open() / .close()
Loop support Automatic via matching IDs using dynamic data Requires isInLoop prop and auto-suffixing logic
Open/close transition CSS @starting-style progressive enhancement None
Scroll lock Included Not supported

If you’re already using EtchDialog and it’s working for you, there’s no urgent reason to switch. SN Modal is worth reaching for when you need multiple triggers, auto-open behavior, loop contexts, or tighter control over styling consistency across dialogs.

Getting Started

Drop the SN Modal component onto your page and add your content inside the slot. To open the dialog, add data-dialog-open to any element on the page and set the value to match the component’s Dialog ID prop:

HTML

Set the Dialog ID prop to my-dialog and you’re done. The trigger and dialog are linked by matching IDs, event delegation handles the rest, and you can have as many triggers pointing to the same dialog as you want.

Props

Label Type Default
Dialog ID Text dialog
Width Select --width-50
Class Class —
Show / Hide (Editor) Boolean false
Trigger Select button

Dialog ID

Links triggers to the dialog. Any element with data-dialog-open="your-id" opens the dialog with a matching ID. It must be unique per page when using multiple dialogs.

Width

Controls max-inline-size via a dropdown of ACSS width variables. The dialog always stays within 100% - 2rem regardless of the chosen value, so it’s safe at any screen size. Swap these variables out if you aren’t using ACSS.

Class

For per-instance styling. Assign a class here and use it to override any of the component’s CSS custom properties on that specific dialog. More on this in the Styling section.

Show / Hide (Editor)

Toggles the dialog open or closed on the Etch canvas so you can edit the content without clicking a trigger. Has no effect on the frontend.

Trigger

Controls how the dialog opens.

Label Behavior
Button Opens only via [data-dialog-open] triggers or the JS API
Additional Trigger Selector When Button is selected, this conditional prop gives you another way to trigger the modal
Auto Opens automatically via timer or scroll depth
Exit Intent Opens when the user’s mouse exits quickly through the top of the viewport

Auto Trigger

When Trigger is set to auto, additional props appear conditionally.

Label Type Default Notes
Auto Trigger Select timer Select from Timer or Scroll Depth
Delay Text 5 If you select Timer, then you will put the number of seconds until it triggers after page load
Scroll Depth Text 500 If you select Scroll Depth, you will put the number of px the user has to scroll to trigger the modal

Timer opens the dialog after a set number of seconds. Scroll Depth opens it once window.scrollY reaches the configured pixel value. If the page is shorter than the target, the dialog fires when the user reaches the bottom instead.

Additional Trigger Selector

An optional CSS selector for triggering the dialog from elements you can’t add data-dialog-open to, like third-party plugin output, menu items, or other markup outside your control. Accepts any valid CSS selector (class, ID, attribute, etc.) and supports multiple matches.

CSS

Exit Intent

When Trigger is set to exit-intent, the dialog opens when the user moves their mouse quickly out through the top of the viewport, toward the browser tabs or URL bar.

This is desktop only by nature. Touch devices have no mouse signal, so the trigger simply never fires there. The velocity gate filters out slow drifts (like moving to a second monitor or reaching for a bookmark), so only a fast upward exit counts.

Repeat Open Suppression

When Trigger is set to auto or exit-intent, cookie props appear conditionally.

Label Type Default
Don’t Show Again Boolean true
Hide For (days) Text 1

When enabled, a cookie (sn_modal_seen_<dialogId>) is written when the user closes an auto-opened dialog. While that cookie exists, the auto or exit-intent trigger won’t fire again. Manual [data-dialog-open] button triggers are never affected by the cookie.

The cookie is written on close, so it only kicks in after the user has actually seen and dismissed the dialog.

To reset during testing, go to DevTools → Application → Cookies and delete sn_modal_seen_*.

Closing the Dialog

You can close the dialog in the following ways:

Closing from a Form

Form plugins like Fluent Forms, WS Form, or Gravity Forms handle submission via Ajax, so the dialog needs to be closed programmatically once the submission completes. Hook into your form plugin’s success event and call the JS API. For example, with Fluent Forms:

JavaScript

Check your form plugin’s documentation for the equivalent success event. The pattern is the same regardless of which plugin you’re using.

Programmatic API

Any dialog can be opened or closed from another snippet using the public API:

JavaScript

The full registry is available at window.snModal.registry, which is a Map keyed by dialog ID.

Using in Loops

Inside an Etch loop, map both the trigger and the dialog ID to the same loop field. Each pair links automatically.

Trigger and Modal (inside the loop):

HTML

Each loop iteration produces a unique trigger/dialog pair. No additional configuration needed.

Styling

CSS Custom Properties

All visual values are controlled through locally-scoped custom properties with empty-variable overrides and fallback defaults. The public --sn-modal-* variables are what you set to customize the component.

Variable Default Description
--sn-modal-bg white Dialog background
--sn-modal-padding var(--space-l, 3rem) Inner content padding
--sn-modal-backdrop rgb(0 0 0 / 0.5) Backdrop overlay color
--sn-modal-radius var(--radius, 0.5rem) Border radius
--sn-modal-close-offset 0.5rem Close button position
--sn-modal-close-size 20px Close button icon size
--sn-modal-close-bg transparent Close button background
--sn-modal-close-radius 100vw Close button radius

Global Customization

The component includes .sn-modal--base in the Class prop by default. It’s pre-wired with all the customization variables as commented-out placeholders. Uncomment and change any value you want to apply across all your modals.

CSS

Per-Instance Override

To style a specific dialog differently, add a second class via the Class prop alongside .sn-modal--base and override whichever variables you need:

CSS

Close Button: Inside vs. Outside

The close button position is controlled entirely by --sn-modal-close-offset:

When positioning the button outside, you’ll typically want a visible background. Set these in .sn-modal--base:

CSS

Open/Close Transition

The component includes a CSS transition using @starting-style and transition-behavior: allow-discrete. Browsers that support it get a smooth fade-and-slide; browsers that don’t simply snap open and closed.

Scroll Lock

Scroll lock is handled automatically via a nested CSS rule inside .sn-modal.

Accessibility

Troubleshooting

Symptom Likely Cause Fix
Trigger does nothing data-dialog-open value doesn’t match dialogId Check for typos or whitespace
Auto trigger doesn’t fire Cookie from a previous test sn_modal_seen_ → Application → Cookies → delete sn_modal_seen_*
Backdrop click doesn’t close Padding on the <dialog> element Padding goes on .sn-modal__body, not .sn-modal
Dialog not centered CSS reset overriding margin: auto Ensure .sn-modal has margin: auto
Exit intent never fires Mouse exited through the side, or too slowly Must exit through the top of the viewport with a fast upward motion