SolidJS Dismissible Layers Utility
solid-dismissible is a foundational utility for SolidJS applications, designed to manage the dismissal behavior of UI layers such as dialogs, dropdowns, or tooltips. It provides a headless API, allowing developers to integrate dismissible logic without imposing specific styling or DOM structure. Key features include support for arbitrarily nested dismissible layers, where only the topmost active layer responds to dismiss actions, and multiple dismissal strategies such as outside pointer events (down/up), loss of focus outside the component, and the Escape key. Each strategy can be individually enabled, disabled, or customized. The current version is 0.1.1, and its last publish date was 2 years ago, indicating it is an early-stage library, part of the broader @corvu UI primitives ecosystem for SolidJS. Due to its early version, the API is subject to potential breaking changes in future minor releases, though its core functionality for handling dismissal logic is well-defined. This utility differentiates itself by offering robust nesting capabilities and fine-grained control over dismissal events, making it a powerful tool for complex interactive UIs.
Common errors
-
TypeError: Cannot read properties of null (reading 'ownerDocument')
cause The `element` prop passed to `Dismissible` was null or undefined when the component attempted to register global event listeners.fixEnsure the DOM element for `Dismissible` is rendered and its ref (`element` prop) is available before `Dismissible` attempts to initialize. Use SolidJS's `createSignal` for the ref and conditionally render the dismissible content with `Show` or `onMount` to guarantee the element exists. -
Dismissible layer not closing when expected (e.g., clicking outside or pressing Escape)
cause The `enabled` prop is `false`, or the `onDismiss` callback does not correctly update the state controlling the `enabled` prop.fixVerify that `enabled={true}` when the dismissible layer should be active. Confirm that the `onDismiss` callback correctly updates the state variable (e.g., `setOpen(false)`) that controls the `enabled` prop, causing the component to react and dismiss. -
Nested dismissible layers close prematurely or in the wrong order.
cause While `solid-dismissible` supports nesting, the parent layers might not be correctly 'disabled' or 'paused' when a child layer is active, causing parent dismissal events to propagate.fixEnsure that when a child dismissible layer is `enabled`, any parent dismissible layers have their `enabled` prop dynamically set to `false` or leverage the `activeDismissibles` signal to prevent parent dismissal logic from firing until the child is dismissed. The library handles the core nesting logic, but your state management must reflect the active layer hierarchy.
Warnings
- breaking As a package in early development (version 0.1.1), `solid-dismissible` may introduce breaking changes in future minor versions (e.g., 0.2.0, 0.3.0) without adhering strictly to semantic versioning until version 1.0.0 is reached.
- gotcha Incorrectly configuring the `element` prop or allowing it to be `null` or `undefined` can lead to the dismissible component not functioning, as it relies on a valid DOM element to track outside interactions.
- gotcha When nesting `Dismissible` components, improper management of the `enabled` prop or `onDismiss` callbacks can lead to unintended dismissal of parent layers or prevent deeper layers from dismissing correctly. The library is designed to support nesting, but careful state management is required.
Install
-
npm install solid-dismissible -
yarn add solid-dismissible -
pnpm add solid-dismissible
Imports
- Dismissible
import { Dismissible } from 'solid-dismissible'import Dismissible from 'solid-dismissible'
- activeDismissibles
import activeDismissibles from 'solid-dismissible'
import { activeDismissibles } from 'solid-dismissible' - DismissibleProps
import type { DismissibleProps } from 'solid-dismissible'
Quickstart
import { createSignal, Show, type Component } from 'solid-js';
import Dismissible from 'solid-dismissible';
const DialogContent: Component<{ open: boolean; setOpen: (open: boolean) => void }> = (props) => {
const [contentRef, setContentRef] = createSignal<HTMLElement | null>(null);
return (
<Dismissible
element={contentRef}
enabled={props.open()}
onDismiss={() => props.setOpen(false)}
// Optional: Prevent dismissal on outside focus
// dismissOnOutsideFocus={false}
// Optional: Prevent dismissal on escape key
// dismissOnEscape={false}
>
<Show when={props.open()}>
<div
ref={setContentRef}
tabIndex={-1} // Make div focusable for testing focus dismissal
style={{
border: '1px solid gray',
padding: '20px',
background: 'white',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1000
}}
>
<h2>Dismissible Dialog</h2>
<p>Click outside or press Escape to dismiss.</p>
<button onClick={() => props.setOpen(false)}>Close</button>
</div>
</Show>
</Dismissible>
);
};
const App: Component = () => {
const [dialogOpen, setDialogOpen] = createSignal(false);
return (
<div>
<button onClick={() => setDialogOpen(true)}>Open Dialog</button>
<DialogContent open={dialogOpen} setOpen={setDialogOpen} />
<p>This is content behind the dialog.</p>
<p>Try opening the dialog and clicking outside or pressing Escape.</p>
</div>
);
};
export default App;