Components
Offcanvas
Offcanvas is the trigger/target/backdrop behavior underneath any slide-in panel — it doesn't require a particular visual treatment. Drawer is that behavior paired with the tf-drawer/tf-sheet styling; this page documents the behavior itself, so it also works with a panel you style yourself.
How It Works
A trigger with data-tf-toggle="offcanvas" and data-tf-target opens any element by ID — the target doesn't need a specific class. Opening inserts a .tf-offcanvas-backdrop, locks background scroll, moves focus into the panel, and traps Tab inside it. Closing removes the backdrop, unlocks scroll, and restores focus to whichever element had it before the panel opened.
Keyboard model
Escape closes the panel. Tab and Shift+Tab cycle only through focusable elements inside it — focus can't escape to the page behind while it's open.
Dismissal
Any element inside the panel with data-tf-dismiss="offcanvas" closes it, in addition to Escape and clicking the backdrop.
Programmatic control
TicFrame.Offcanvas.show/hide/toggle(target) drive a panel from script — useful for opening one from a keyboard shortcut or another flow with no rendered trigger button.
Content-agnostic
The target can be any element — give it your own layout classes, or reach for tf-drawer/tf-sheet for the ready-made side/bottom panel treatment.
Live Examples
Basic panel (no styling opinion)
Offcanvas doesn't require tf-drawer. Here the target is a plain <aside> styled with ordinary utilities, still getting the full backdrop/focus-trap/Escape contract.
<button type="button" class="tf-btn tf-btn-primary" data-tf-toggle="offcanvas" data-tf-target="#offcanvas-basic">Open panel</button>
<aside id="offcanvas-basic" aria-label="Basic offcanvas panel" class="tf-fixed tf-inset-block-0 tf-end-0 tf-w-64 tf-max-w-full tf-bg-surface-raised tf-shadow-2 tf-p-6"
style="z-index: var(--tf-z-modal); inline-size: min(20rem, 90vw);">
<button type="button" class="tf-btn tf-btn-icon tf-btn-ghost" data-tf-dismiss="offcanvas" aria-label="Close panel"><i class="ti ti-028069" aria-hidden="true"></i></button>
<p>A plain element, no tf-drawer class -- the behavior doesn't care.</p>
</aside>Multiple independent triggers
Any number of triggers can point at the same or different panels — each registers independently.
<button type="button" class="tf-btn" data-tf-toggle="offcanvas" data-tf-target="#offcanvas-shared">Open from button A</button>
<button type="button" class="tf-btn" data-tf-toggle="offcanvas" data-tf-target="#offcanvas-shared">Open from button B</button>
<aside id="offcanvas-shared" class="tf-drawer" aria-label="Shared offcanvas panel">...</aside>Programmatic API
The same panel, driven entirely from TicFrame.Offcanvas instead of a data-tf-toggle trigger.
TicFrame.Offcanvas.show("#offcanvas-basic");
TicFrame.Offcanvas.hide("#offcanvas-basic");
TicFrame.Offcanvas.toggle("#offcanvas-basic");Accessibility
| Requirement | TicFrame guidance |
|---|---|
| Role | TicFrame sets role="dialog" and aria-modal="true" on the target automatically. |
| Name | Add aria-label or aria-labelledby on the target — TicFrame doesn't infer one. |
| Focus | The first focusable element inside the panel receives focus on open; focus returns to the trigger (or whichever element had it) on close. |
| Focus trap | Tab and Shift+Tab cycle only within the panel while it's open, so keyboard users can't tab into obscured background content. |
| Keyboard | Escape always closes the panel — there's no keyboard-disable option, unlike Modal's static backdrop. |
| Icon-only dismiss | Give an icon-only close button its own aria-label; the icon itself stays aria-hidden. |
API Reference
Attributes
| Attribute | Where | Purpose |
|---|---|---|
data-tf-toggle="offcanvas" | Trigger | Marks the element as an offcanvas trigger. |
data-tf-target | Trigger | Selector for the panel to open (required). |
data-tf-dismiss="offcanvas" | Any element inside the panel | Closes the panel on click. |
Methods
| Method | Arguments | Return |
|---|---|---|
TicFrame.Offcanvas.show(target) | Selector or element | Panel element or null if already open or not found. |
TicFrame.Offcanvas.hide(target) | Selector or element | Panel element or null if already closed or not found. |
TicFrame.Offcanvas.toggle(target) | Selector or element | Panel element or null if not found. |
Events
| Event | When it fires | Target |
|---|---|---|
tf:offcanvas:open | After the panel opens. | Panel |
tf:offcanvas:close | After the panel closes. | Panel |
CSS Variables
| Token | Default | Purpose |
|---|---|---|
--tf-z-modal | 1300 | Panel stacking order (the panel is given this z-index directly when you build your own, as in the basic example above). |
--tf-z-modal - 1 | 1299 | Backdrop stacking order — always one layer below the panel. |
Troubleshooting
| Symptom | Fix |
|---|---|
| Clicking the trigger does nothing | data-tf-target must match an element that exists in the DOM at TicFrame.init() time — check the selector and that the panel isn't rendered later by other script. |
| Backdrop stays after the panel is removed by other script | Call TicFrame.Offcanvas.hide(panel) before removing the panel from the DOM, rather than removing it directly. |
| Panel appears behind other content | Give a custom (non-tf-drawer) panel z-index: var(--tf-z-modal) directly, as shown in the basic example. |
| Focus trap conflicts with a nested dialog | Don't open a Modal from inside an offcanvas panel without closing the panel first — two independent focus traps will fight over Tab. |