Components
Drawer
tf-drawer and tf-sheet are the ready-made panel treatments for the Offcanvas trigger/backdrop behavior — a drawer slides in from the side, a sheet slides up from the bottom.
How It Works
Give any element tf-drawer or tf-sheet and pair it with a data-tf-toggle="offcanvas" trigger — the classes only supply positioning, size, and the slide transition. Everything else (backdrop, focus trap, Escape, scroll lock) comes from Offcanvas.
Drawer vs. sheet
tf-drawer is anchored to the inline-end edge (right in LTR, left in RTL) and slides horizontally. tf-sheet is anchored to the bottom edge, spans full width, and slides vertically — the mobile-native pattern.
RTL
A drawer's slide direction mirrors automatically under dir="rtl" — no separate class needed. A sheet is unaffected by direction since it moves vertically.
Keyboard model
Identical to Offcanvas: Escape closes, Tab is trapped inside while open, focus returns to the trigger on close.
Sizing
A drawer defaults to min(28rem, 90vw) wide. A sheet is always full width and caps its height at 85dvb so tall content scrolls internally instead of overflowing the viewport.
Live Examples
Drawer (default, side-anchored)
Slides in from the inline-end edge.
<button type="button" class="tf-btn tf-btn-primary" data-tf-toggle="offcanvas" data-tf-target="#drawer-basic">Open drawer</button>
<aside id="drawer-basic" class="tf-drawer" aria-label="Basic drawer">
<button type="button" class="tf-btn tf-btn-icon tf-btn-ghost" data-tf-dismiss="offcanvas" aria-label="Close drawer"><i class="ti ti-028069" aria-hidden="true"></i></button>
<p>Slides from the inline-end edge; mirrors under RTL automatically.</p>
</aside>Sheet (bottom-anchored)
Full width, slides up from the bottom, rounded top corners only — the mobile-native pattern. Try it at a narrow viewport width.
<button type="button" class="tf-btn tf-btn-primary" data-tf-toggle="offcanvas" data-tf-target="#sheet-basic">Open sheet</button>
<aside id="sheet-basic" class="tf-sheet" aria-label="Basic sheet">
<button type="button" class="tf-btn tf-btn-icon tf-btn-ghost" data-tf-dismiss="offcanvas" aria-label="Close sheet"><i class="ti ti-028069" aria-hidden="true"></i></button>
<p>Anchored to the bottom edge, full width, rounded top corners.</p>
</aside>Drawer with header/body/footer structure
A drawer is unopinionated about internal layout — compose it with ordinary utilities for a header, scrollable body, and footer.
<aside id="drawer-structured" class="tf-drawer tf-flex tf-flex-col" aria-label="Shopping cart">
<div class="tf-flex tf-items-center tf-justify-between tf-pb-4 tf-mb-4">
<h4><i class="ti ti-022275" aria-hidden="true"></i> Your cart</h4>
<button type="button" data-tf-dismiss="offcanvas" aria-label="Close cart"><i class="ti ti-028069" aria-hidden="true"></i></button>
</div>
<div class="tf-flex-1 tf-stack">...line items...</div>
<div><button type="button" class="tf-btn tf-btn-primary tf-w-full">Checkout</button></div>
</aside>States
A drawer has two states, reflected on the panel itself and readable by both CSS and script.
| State | How it's represented |
|---|---|
| Closed | hidden attribute present; translated fully off-screen so it doesn't participate in layout or focus order. |
| Open | hidden removed; translated to 0, focus moved inside, backdrop present. |
| Dismiss button focus | Standard :focus-visible ring — no drawer-specific override. |
| Trigger disabled | A disabled trigger button does not open the drawer, same as any native disabled control. |
Accessibility
| Requirement | TicFrame guidance |
|---|---|
| Name | Add aria-label or aria-labelledby on the drawer/sheet element. |
| Role and focus | Inherited entirely from Offcanvas — see the Offcanvas accessibility table. |
| Reduced motion | The slide transition respects prefers-reduced-motion through the shared --tf-duration-normal token. |
API Reference
Classes
| Class | Purpose |
|---|---|
tf-drawer | Side-anchored panel, slides horizontally. |
tf-sheet | Bottom-anchored panel, slides vertically, full width, rounded top corners. |
Attributes, methods, and events are the same as Offcanvas — see the Offcanvas API reference.
CSS Variables
| Token | Default | Purpose |
|---|---|---|
--tf-z-modal | 1300 | Drawer/sheet stacking order. |
--tf-shadow-2 | Elevation shadow token | Panel shadow. |
--tf-radius-6 | Large radius token | Sheet's top-corner rounding. |
--tf-space-6 | Spacing token | Panel padding. |
--tf-duration-normal | 200ms | Slide transition duration. |
Troubleshooting
| Symptom | Fix |
|---|---|
| Sheet slides from the side instead of the bottom | Make sure you're on TicFrame 5.7.0-alpha.1 or later — earlier alpha builds shared drawer's side-slide CSS for tf-sheet. |
| Drawer doesn't slide, just appears/disappears | Don't toggle display yourself; let TicFrame manage the hidden attribute so the translate transition can run. |
| Sheet content is cut off on short viewports | Content taller than 85dvb scrolls internally — the sheet itself has overflow: auto, so no extra markup is needed. |
| Drawer opens on the wrong side under RTL | This is expected: it mirrors automatically. If you need a fixed side regardless of direction, override inset-inline-end with a physical right/left value. |