Modals — Unified Overlay System
Headless overlay system (modal, fullscreen, top/bottom banner, slide-in, toast) with eligibility (targeting, date window, priority), client-side frequency capping, trigger scheduling and a provider pattern (no built-in DB/backend). React and Svelte adapters, mobile-first and accessible.
Variables de entorno
Este componente no requiere variables de entorno.
Instalación manual
Descarga el tarball y copia cada archivo listado abajo a su ruta destino:
curl -L -o modals.tar.gz https://TU_HOST/registry/modals.tar.gz
tar -xzf modals.tar.gz | Origen | Destino en tu proyecto |
|---|---|
core/types.ts | src/modularcore/modals/core/types.ts |
core/storage.ts | src/modularcore/modals/core/storage.ts |
core/frequency.ts | src/modularcore/modals/core/frequency.ts |
core/eligibility.ts | src/modularcore/modals/core/eligibility.ts |
core/provider.ts | src/modularcore/modals/core/provider.ts |
core/providers/in-memory.ts | src/modularcore/modals/core/providers/in-memory.ts |
core/triggers.ts | src/modularcore/modals/core/triggers.ts |
core/modals.ts | src/modularcore/modals/core/modals.ts |
adapters/react/use-modals.ts | src/modularcore/modals/adapters/react/use-modals.ts |
adapters/svelte/create-modals.svelte.ts | src/modularcore/modals/adapters/svelte/create-modals.svelte.ts |
ui/a11y/focus-trap.ts | src/modularcore/modals/ui/a11y/focus-trap.ts |
ui/a11y/reduced-motion.ts | src/modularcore/modals/ui/a11y/reduced-motion.ts |
ui/safe/url.ts | src/modularcore/modals/ui/safe/url.ts |
ui/safe/style.ts | src/modularcore/modals/ui/safe/style.ts |
ui/react/internal/safe-render.ts | src/modularcore/modals/ui/react/internal/safe-render.ts |
ui/react/internal/use-escape-key.ts | src/modularcore/modals/ui/react/internal/use-escape-key.ts |
ui/react/internal/OverlayBody.tsx | src/modularcore/modals/ui/react/internal/OverlayBody.tsx |
ui/react/ModalOverlay.tsx | src/modularcore/modals/ui/react/ModalOverlay.tsx |
ui/react/FullscreenOverlay.tsx | src/modularcore/modals/ui/react/FullscreenOverlay.tsx |
ui/react/TopBanner.tsx | src/modularcore/modals/ui/react/TopBanner.tsx |
ui/react/BottomBanner.tsx | src/modularcore/modals/ui/react/BottomBanner.tsx |
ui/react/SlideIn.tsx | src/modularcore/modals/ui/react/SlideIn.tsx |
ui/react/Toast.tsx | src/modularcore/modals/ui/react/Toast.tsx |
ui/react/ModalsRenderer.tsx | src/modularcore/modals/ui/react/ModalsRenderer.tsx |
ui/svelte/internal/OverlayBody.svelte | src/modularcore/modals/ui/svelte/internal/OverlayBody.svelte |
ui/svelte/ModalOverlay.svelte | src/modularcore/modals/ui/svelte/ModalOverlay.svelte |
ui/svelte/FullscreenOverlay.svelte | src/modularcore/modals/ui/svelte/FullscreenOverlay.svelte |
ui/svelte/TopBanner.svelte | src/modularcore/modals/ui/svelte/TopBanner.svelte |
ui/svelte/BottomBanner.svelte | src/modularcore/modals/ui/svelte/BottomBanner.svelte |
ui/svelte/SlideIn.svelte | src/modularcore/modals/ui/svelte/SlideIn.svelte |
ui/svelte/Toast.svelte | src/modularcore/modals/ui/svelte/Toast.svelte |
ui/svelte/ModalsRenderer.svelte | src/modularcore/modals/ui/svelte/ModalsRenderer.svelte |
Instalación con CLI
modularcore add modalsDocumentación
# @modularcore/modals
Headless, unified overlay system — modal, fullscreen, top banner, bottom banner, slide-in, and
toast — with eligibility (targeting, date window, priority), client-side frequency capping,
trigger scheduling, and a provider pattern (no built-in DB/backend). React and Svelte 5
adapters, mobile-first and accessible.
No database, no built-in backend.
between the core and any data source:
persisted analytics. This package ships
implementation and no more — see
[
to wire a real backend behind the same interface.
## What's in this package
-
frequency, targeting, buttons).
-
-
this is client-side instead of server-side.
-
-
one winner per singleton slot (
schedules triggers, and shows/dismisses while notifying subscribers.
-
implementation.
-
Svelte 5 runes). One manager instance per hook/rune, with
scroll/mouseout/timeout listeners are always cleaned up.
-
URL/color-validation helpers, consumed by both
-
maps manager state to them.
## Security boundary: the provider is untrusted content
so
themselves (not delegated to the consumer):
-
- Button
and anything else is dropped, falling back to a plain
supplied URL.
- External links always get
-
before ever reaching a
free-form style string.
## Basic usage (React)
## Basic usage (Svelte 5)
## Accessibility & responsive design
and expose
full-width/near-full-width on narrow viewports, no horizontal scroll, and transitions are skipped
when
See
responsive/a11y contract table per overlay type.
Headless, unified overlay system — modal, fullscreen, top banner, bottom banner, slide-in, and
toast — with eligibility (targeting, date window, priority), client-side frequency capping,
trigger scheduling, and a provider pattern (no built-in DB/backend). React and Svelte 5
adapters, mobile-first and accessible.
No database, no built-in backend.
ModalsProvider (see core/provider.ts) is the only seambetween the core and any data source:
getActiveModals(ctx) returns raw candidates, andtrackView/trackInteraction are hooks a consumer wires to their own backend if they wantpersisted analytics. This package ships
core/providers/in-memory.ts as a referenceimplementation and no more — see
[
docs/prisma-tracking-endpoint-example.md](./docs/prisma-tracking-endpoint-example.md) for howto wire a real backend behind the same interface.
## What's in this package
-
core/types.ts — the framework-agnostic ModalConfig model (6 overlay types, triggers,frequency, targeting, buttons).
-
core/eligibility.ts — pure filter: isActive → date window → targeting → frequency.-
core/frequency.ts + core/storage.ts — client-side frequency capping (always /once-per-session / once-per-day / once-ever) via injectable sessionStorage/localStorage. See [docs/frequency-client-side.md](./docs/frequency-client-side.md) for whythis is client-side instead of server-side.
-
core/triggers.ts — trigger scheduling (page-load/delay, scroll, exit-intent, click,manual) against an injectable TriggerEnvironment.-
core/modals.ts — OverlayManager, the headless orchestrator: resolves eligible configs intoone winner per singleton slot (
modal/fullscreen share a slot) plus a capped toast stack,schedules triggers, and shows/dismisses while notifying subscribers.
-
core/provider.ts, core/providers/in-memory.ts — the provider seam + referenceimplementation.
-
adapters/react, adapters/svelte — thin bindings over OverlayManager (Svelte adapter usesSvelte 5 runes). One manager instance per hook/rune, with
destroy() wired to unmount soscroll/mouseout/timeout listeners are always cleaned up.
-
ui/a11y/, ui/safe/ — shared, framework-agnostic focus-trap/reduced-motion andURL/color-validation helpers, consumed by both
ui/react and ui/svelte.-
ui/react/, ui/svelte/ — one component set per overlay type, plus ModalsRenderer thatmaps manager state to them.
## Security boundary: the provider is untrusted content
getActiveModals() results are content rendered into the DOM. message, imageUrl, and buttonurl may originate from a database or CMS with lower trust than the code calling this package —so
ui/react/ui/svelte treat every ModalConfig as untrusted and apply this boundarythemselves (not delegated to the consumer):
-
message renders as plain text by default. Only when allowHtml: true is it passed throughrenderMarkdownToHtml (from @modularcore/ai-chat/markdown, Markdown-only — never raw HTML).- Button
url and imageUrl go through an allowlist (ui/safe/url.ts): https:/http:/mailto:/tel: for links, https: (or size-capped opt-in data:) for images. javascript:and anything else is dropped, falling back to a plain
<button>/no image instead of thesupplied URL.
- External links always get
rel="noopener noreferrer"; images getreferrerpolicy="no-referrer".-
bgColor/textColor are validated against a hex/rgb()/rgba() pattern (ui/safe/style.ts)before ever reaching a
style attribute; maxWidth is a closed enum mapped to a class, never afree-form style string.
## Basic usage (React)
import { useModals } from '@modularcore/modals/react';
import { ModalsRenderer } from '@modularcore/modals/ui/react/ModalsRenderer';
import { createInMemoryProvider } from '@modularcore/modals/providers/in-memory';
const provider = createInMemoryProvider({
modals: [
{
id: 'welcome',
type: 'top-banner',
message: 'Welcome! 20% off today.',
trigger: { type: 'delay', value: 2000 },
},
],
});
function App() {
const { state, dismiss } = useModals(provider, { path: window.location.pathname });
return <ModalsRenderer state={state} onDismiss={dismiss} />;
}
## Basic usage (Svelte 5)
<script lang="ts">
import { createModals } from '@modularcore/modals/svelte';
import ModalsRenderer from '@modularcore/modals/ui/svelte/ModalsRenderer.svelte';
import { createInMemoryProvider } from '@modularcore/modals/providers/in-memory';
const provider = createInMemoryProvider({ modals: [/* ... */] });
const modals = createModals(provider, { path: '/' }); // must be called during component init
</script>
<ModalsRenderer state={modals.state} ondismiss={modals.dismiss} />
## Accessibility & responsive design
modal/fullscreen trap focus (Tab/Shift+Tab cycle, focus restored on close), close on Escape,and expose
role="dialog" aria-modal="true". top-banner/bottom-banner/slide-in/toast usearia-live="polite" (toast: role="status") and never steal focus. Every type is mobile-first:full-width/near-full-width on narrow viewports, no horizontal scroll, and transitions are skipped
when
prefers-reduced-motion is set (checked via ui/a11y/reduced-motion.ts).See
plans/260825-1940-modals-overlay-system/plan.md for the full design, including theresponsive/a11y contract table per overlay type.