Components
Modal
TicFrame modals are native dialog elements styled and enhanced by the framework. Use them for blocking decisions, focused forms, destructive confirmations, and short flows that need the user’s full attention.
How It Works
A modal opens from a trigger with data-tf-toggle="modal" and data-tf-target, or from TicFrame.Modal.show(). TicFrame updates optional content slots, calls the native dialog open path, locks background scrolling, focuses the first useful control, and restores focus to the opener when the dialog closes.
Lifecycle
Open, close, static-backdrop, and update events use the tf:modal:* namespace. Dismiss buttons use data-tf-dismiss="modal".
Native dialog
The component uses <dialog>, including the platform backdrop and Escape behavior. TicFrame adds tokens, layout variants, media cleanup, and predictable data APIs.
Stacking
Avoid placing modals inside transformed or clipped containers. Modal-to-modal triggers close the current dialog before opening the next one.
Motion
Use tf-modal-fade for motion and tf-modal-no-animation when the dialog should appear immediately. Reduced-motion users receive an immediate transition.
Anatomy
The rendered anatomy below is a static documentation preview, not an overlay. A live modal uses the same structure inside a dialog.tf-modal.
Modal title
The body contains the decision, form, or supporting content.
<dialog id="settings-modal" class="tf-modal tf-modal-md" aria-labelledby="settings-modal-title" aria-describedby="settings-modal-description">
<div class="tf-modal-header">
<h2 id="settings-modal-title" class="tf-modal-title">Modal title</h2>
<button type="button" class="tf-btn tf-btn-icon tf-btn-ghost" data-tf-dismiss="modal" aria-label="Close modal">
<i class="ti ti-028069" aria-hidden="true"></i>
</button>
</div>
<div id="settings-modal-description" class="tf-modal-body">Modal body text goes here.</div>
<div class="tf-modal-footer">
<button type="button" class="tf-btn" data-tf-dismiss="modal">Cancel</button>
<button type="button" class="tf-btn tf-btn-primary" data-tf-dismiss="modal">Save changes</button>
</div>
</dialog>
Live Examples
Each example includes the rendered trigger and complete source needed to reproduce it.
Basic live modal
Open a modal, close it from the header, footer, backdrop, or Escape key.
<button type="button" class="tf-btn tf-btn-primary" data-tf-toggle="modal" data-tf-target="#demo-modal">Open modal</button>
<dialog id="demo-modal" class="tf-modal tf-modal-md" aria-labelledby="demo-modal-title" aria-describedby="demo-modal-description">
<div class="tf-modal-header">
<h2 id="demo-modal-title" class="tf-modal-title">Save workspace</h2>
<button type="button" class="tf-btn tf-btn-icon tf-btn-ghost" data-tf-dismiss="modal" aria-label="Close modal"><i class="ti ti-028069" aria-hidden="true"></i></button>
</div>
<div id="demo-modal-description" class="tf-modal-body">Review your changes before continuing.</div>
<div class="tf-modal-footer"><button type="button" class="tf-btn" data-tf-dismiss="modal">Cancel</button><button type="button" class="tf-btn tf-btn-primary" data-tf-dismiss="modal">Save changes</button></div>
</dialog>Static backdrop and keyboard lock
Use these options when the modal needs an explicit decision.
<dialog class="tf-modal tf-modal-md" data-tf-modal-backdrop="static" data-tf-modal-keyboard="false" aria-labelledby="required-step-title">
<div class="tf-modal-header"><h2 id="required-step-title" class="tf-modal-title">Complete required step</h2></div>
<div class="tf-modal-body">Backdrop clicks and Escape are blocked until the user chooses an explicit action.</div>
<div class="tf-modal-footer"><button type="button" class="tf-btn tf-btn-primary" data-tf-dismiss="modal">I understand</button></div>
</dialog>Scrolling and centered variants
Long content can scroll as a whole surface, or only the modal body can scroll while header and footer stay visible.
<dialog class="tf-modal tf-modal-md tf-modal-scrollable" aria-labelledby="review-title">...</dialog>
<dialog class="tf-modal tf-modal-md tf-modal-centered" aria-labelledby="centered-title">...</dialog>
<dialog class="tf-modal tf-modal-md tf-modal-centered tf-modal-scrollable" aria-labelledby="centered-scroll-title">...</dialog>Rich and dynamic content
Tooltips, popovers, grids, varying trigger content, and modal-to-modal transitions work inside dialogs.
<button data-tf-toggle="modal" data-tf-target="#varying-modal" data-tf-modal-title="Message Ada" data-tf-modal-body="Compose a note for Ada.">Message Ada</button>
<dialog id="varying-modal" class="tf-modal tf-modal-md" aria-labelledby="varying-modal-title">
<h2 id="varying-modal-title" data-tf-modal-slot="title" class="tf-modal-title">Message recipient</h2>
<p data-tf-modal-slot="body">Choose a recipient to update this copy.</p>
</dialog>Animation, media cleanup, sizes, and fullscreen
Use explicit classes for motion, sizing, and responsive fullscreen behavior.
<dialog class="tf-modal tf-modal-fade">...</dialog>
<dialog class="tf-modal tf-modal-no-animation">...</dialog>
<dialog class="tf-modal tf-modal-lg" data-tf-modal-reset-media>...</dialog>
<dialog class="tf-modal tf-modal-sm">...</dialog>
<dialog class="tf-modal tf-modal-md">...</dialog>
<dialog class="tf-modal tf-modal-lg">...</dialog>
<dialog class="tf-modal tf-modal-xl">...</dialog>
<dialog class="tf-modal tf-modal-fullscreen-md-down">...</dialog>
<script>TicFrame.Modal.update("#dynamic-height-modal");</script>Representative Patterns
Use these small examples as common starting points. The complete 19-pattern gallery is available in Modal patterns.
<dialog class="tf-modal tf-modal-md tf-modal-center" aria-labelledby="confirm-title">
<div class="tf-modal-hero">
<span class="tf-modal-icon tf-modal-icon-success"><i class="ti ti-004292" aria-hidden="true"></i></span>
<h2 id="confirm-title" class="tf-text-2xl tf-font-semibold">Publish changes?</h2>
<p class="tf-text-muted">Your updates will be visible to everyone with access.</p>
<div class="tf-flex tf-justify-center tf-gap-3"><button type="button" class="tf-btn" data-tf-dismiss="modal">Review</button><button type="button" class="tf-btn tf-btn-primary" data-tf-dismiss="modal">Publish</button></div>
</div>
</dialog>Accessibility
| Requirement | TicFrame guidance |
|---|---|
| Name | Use aria-labelledby pointing at the modal title, or provide an equivalent accessible name. |
| Description | Use aria-describedby for important explanatory copy. |
| Close control | Icon-only close buttons must include an aria-label. Use Ticons UID classes for the visible icon. |
| Keyboard | Escape closes normal modals. Static or keyboard-disabled modals must provide a visible action. |
| Focus | TicFrame focuses the first useful control and restores focus to the opening trigger on close. |
| Destructive actions | Place the safe action first and use autofocus on it when the risk is high. |
| Motion and contrast | Animations honor reduced motion. Tokens inherit dark, RTL, and high-contrast theme behavior. |
API Reference
Structure and classes
| Class | Purpose |
|---|---|
tf-modal | Required dialog surface. |
tf-modal-header, tf-modal-body, tf-modal-footer | Optional structure regions. |
tf-modal-title | Title styling for the labelled heading. |
tf-modal-centered, tf-modal-scrollable | Vertical centering and body-only scrolling. |
tf-modal-fade, tf-modal-no-animation | Animation variants. |
tf-modal-sm, tf-modal-md, tf-modal-lg, tf-modal-xl | Explicit modal widths. |
tf-modal-fullscreen* | Always or responsive fullscreen behavior. |
Options
| Property | Data attribute | Type | Default | Description |
|---|---|---|---|---|
backdrop | data-tf-modal-backdrop | "dismiss" | "static" | "dismiss" | Controls backdrop click behavior. |
keyboard | data-tf-modal-keyboard | boolean | true | Set to false to prevent Escape dismissal. |
title | data-tf-modal-title | string | null | Trigger-provided text for title slots. |
body | data-tf-modal-body | string | null | Trigger-provided text for body slots. |
resetMedia | data-tf-modal-reset-media | boolean | false | Pauses audio/video and reloads iframes on close. |
Methods
| Method | Arguments | Return | Timing |
|---|---|---|---|
TicFrame.Modal.show(target, trigger?) | Selector or dialog, optional trigger | Dialog or null | Synchronous open request. |
TicFrame.Modal.hide(target) | Selector or dialog | Dialog or null | Synchronous close request. |
TicFrame.Modal.toggle(target, trigger?) | Selector or dialog, optional trigger | Dialog or null | Synchronous open/close request. |
TicFrame.Modal.update(target) | Selector or dialog | Dialog or null | Dispatches update for dynamic content. |
Events
| Event | When it fires | Target | Detail | Cancelable |
|---|---|---|---|---|
tf:modal:open | After TicFrame opens a dialog. | Dialog | { component, modal, trigger } | No |
tf:modal:close | After the dialog closes. | Dialog | { component, modal } | No |
tf:modal:static | When backdrop/Escape dismissal is refused. | Dialog | { component, modal } | No |
tf:modal:update | When update() is called. | Dialog | { component, modal } | No |
Sizes and Fullscreen
Modal sizes
| Name | Class | Maximum width | Recommended use |
|---|---|---|---|
| Default | tf-modal | --tf-modal-inline-size, 36rem by default | General dialogs. |
| Small | tf-modal-sm | 18.75rem | Short confirmations. |
| Medium | tf-modal-md | 31.25rem | Forms and focused decisions. |
| Large | tf-modal-lg | 50rem | Tables, grids, media previews. |
| Extra large | tf-modal-xl | 71.25rem | Wide application flows. |
Fullscreen variants
| Class | Breakpoint | Behavior |
|---|---|---|
tf-modal-fullscreen | All | Always fills the viewport. |
tf-modal-fullscreen-sm-down | Small and below | Fullscreen on small screens. |
tf-modal-fullscreen-md-down | Medium and below | Fullscreen on medium screens and smaller. |
tf-modal-fullscreen-lg-down | Large and below | Fullscreen on large screens and smaller. |
tf-modal-fullscreen-xl-down | Extra-large and below | Fullscreen below the xl boundary. |
tf-modal-fullscreen-2xl-down | 2xl and below | Fullscreen below the widest breakpoint. |
tf-modal-fullscreen-xxl-down | Alias | Migration alias for teams using xxl wording. |
CSS Variables
| Token | Default | Purpose |
|---|---|---|
--tf-modal-inline-size | 36rem | Base modal width. |
--tf-modal-max-inline-size | 92vw | Viewport width cap. |
--tf-modal-max-block-size | calc(100dvb - var(--tf-space-8)) | Viewport height cap. |
--tf-modal-padding | var(--tf-space-6) | Body and structural padding. |
--tf-modal-gap | var(--tf-space-3) | Header/footer item gap. |
--tf-modal-border | var(--tf-border-default) | Surface border. |
--tf-modal-radius | var(--tf-radius-6) | Surface radius. |
--tf-modal-shadow | var(--tf-shadow-2) | Surface shadow. |
--tf-modal-backdrop | rgb(15 23 42 / 0.62) | Native backdrop color. |
--tf-modal-animation-duration | var(--tf-duration-200) | Fade duration. |
--tf-modal-animation-easing | var(--tf-ease-standard) | Fade easing. |
Migration Mapping
Use this table when translating a modal from another framework into TicFrame naming. Rebuild the markup with TicFrame classes rather than copying the original implementation.
| Common pattern | TicFrame equivalent |
|---|---|
| Modal root | dialog.tf-modal |
| Header/body/footer regions | tf-modal-header, tf-modal-body, tf-modal-footer |
| Title | tf-modal-title plus aria-labelledby |
| Static backdrop | data-tf-modal-backdrop="static" |
| Keyboard disabled | data-tf-modal-keyboard="false" |
| Centered | tf-modal-centered |
| Scrollable body | tf-modal-scrollable |
| Responsive fullscreen | tf-modal-fullscreen-*-down |
Troubleshooting
| Symptom | Fix |
|---|---|
| Modal appears behind content | Keep the dialog near the end of the document and out of transformed, clipped, or isolated stacking contexts. |
| Title is not announced | Point aria-labelledby at the visible title or provide an accessible name. |
| Media keeps playing after close | Add data-tf-modal-reset-media. |
| Dynamic content changes the size | Call TicFrame.Modal.update(target) after injecting custom content. |
| Closed scrollable modal is visible | The framework rule dialog.tf-modal:not([open]) keeps all closed dialog variants hidden. |