ModularCore Hub
← Volver al catálogo

Modals — Unified Overlay System

ui v0.1.0 react, svelte

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
OrigenDestino en tu proyecto
core/types.tssrc/modularcore/modals/core/types.ts
core/storage.tssrc/modularcore/modals/core/storage.ts
core/frequency.tssrc/modularcore/modals/core/frequency.ts
core/eligibility.tssrc/modularcore/modals/core/eligibility.ts
core/provider.tssrc/modularcore/modals/core/provider.ts
core/providers/in-memory.tssrc/modularcore/modals/core/providers/in-memory.ts
core/triggers.tssrc/modularcore/modals/core/triggers.ts
core/modals.tssrc/modularcore/modals/core/modals.ts
adapters/react/use-modals.tssrc/modularcore/modals/adapters/react/use-modals.ts
adapters/svelte/create-modals.svelte.tssrc/modularcore/modals/adapters/svelte/create-modals.svelte.ts
ui/a11y/focus-trap.tssrc/modularcore/modals/ui/a11y/focus-trap.ts
ui/a11y/reduced-motion.tssrc/modularcore/modals/ui/a11y/reduced-motion.ts
ui/safe/url.tssrc/modularcore/modals/ui/safe/url.ts
ui/safe/style.tssrc/modularcore/modals/ui/safe/style.ts
ui/react/internal/safe-render.tssrc/modularcore/modals/ui/react/internal/safe-render.ts
ui/react/internal/use-escape-key.tssrc/modularcore/modals/ui/react/internal/use-escape-key.ts
ui/react/internal/OverlayBody.tsxsrc/modularcore/modals/ui/react/internal/OverlayBody.tsx
ui/react/ModalOverlay.tsxsrc/modularcore/modals/ui/react/ModalOverlay.tsx
ui/react/FullscreenOverlay.tsxsrc/modularcore/modals/ui/react/FullscreenOverlay.tsx
ui/react/TopBanner.tsxsrc/modularcore/modals/ui/react/TopBanner.tsx
ui/react/BottomBanner.tsxsrc/modularcore/modals/ui/react/BottomBanner.tsx
ui/react/SlideIn.tsxsrc/modularcore/modals/ui/react/SlideIn.tsx
ui/react/Toast.tsxsrc/modularcore/modals/ui/react/Toast.tsx
ui/react/ModalsRenderer.tsxsrc/modularcore/modals/ui/react/ModalsRenderer.tsx
ui/svelte/internal/OverlayBody.sveltesrc/modularcore/modals/ui/svelte/internal/OverlayBody.svelte
ui/svelte/ModalOverlay.sveltesrc/modularcore/modals/ui/svelte/ModalOverlay.svelte
ui/svelte/FullscreenOverlay.sveltesrc/modularcore/modals/ui/svelte/FullscreenOverlay.svelte
ui/svelte/TopBanner.sveltesrc/modularcore/modals/ui/svelte/TopBanner.svelte
ui/svelte/BottomBanner.sveltesrc/modularcore/modals/ui/svelte/BottomBanner.svelte
ui/svelte/SlideIn.sveltesrc/modularcore/modals/ui/svelte/SlideIn.svelte
ui/svelte/Toast.sveltesrc/modularcore/modals/ui/svelte/Toast.svelte
ui/svelte/ModalsRenderer.sveltesrc/modularcore/modals/ui/svelte/ModalsRenderer.svelte

Instalación con CLI

modularcore add modals

Documentació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. ModalsProvider (see core/provider.ts) is the only seam
between the core and any data source: getActiveModals(ctx) returns raw candidates, and
trackView/trackInteraction are hooks a consumer wires to their own backend if they want
persisted analytics. This package ships core/providers/in-memory.ts as a reference
implementation and no more — see
[docs/prisma-tracking-endpoint-example.md](./docs/prisma-tracking-endpoint-example.md) for how
to 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 why
this 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.tsOverlayManager, the headless orchestrator: resolves eligible configs into
one 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 + reference
implementation.
- adapters/react, adapters/svelte — thin bindings over OverlayManager (Svelte adapter uses
Svelte 5 runes). One manager instance per hook/rune, with destroy() wired to unmount so
scroll/mouseout/timeout listeners are always cleaned up.
- ui/a11y/, ui/safe/ — shared, framework-agnostic focus-trap/reduced-motion and
URL/color-validation helpers, consumed by both ui/react and ui/svelte.
- ui/react/, ui/svelte/ — one component set per overlay type, plus ModalsRenderer that
maps manager state to them.

## Security boundary: the provider is untrusted content

getActiveModals() results are content rendered into the DOM. message, imageUrl, and button
url 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 boundary
themselves (not delegated to the consumer):

- message renders as plain text by default. Only when allowHtml: true is it passed through
renderMarkdownToHtml (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 the
supplied URL.
- External links always get rel="noopener noreferrer"; images get
referrerpolicy="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 a
free-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 use
aria-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 the
responsive/a11y contract table per overlay type.