tiny-drawer
v1.2.0
Published
A production-ready, framework-agnostic drawer component for vanilla JavaScript and TypeScript.
Downloads
462
Maintainers
Readme
tiny-drawer
tiny-drawer is a production-ready drawer component for vanilla JavaScript and TypeScript. It supports top, right, bottom, and left placements, works inside any parent container, ships multiple bundle formats, and includes an optional tiny-engine-core adapter.
Features
- Framework-agnostic API
- TypeScript-first with bundled declarations
- ESM, CJS, and UMD builds
- CSS and minified CSS output
- Draggable resize with pointer events and
requestAnimationFrame - Optional scroll-to-full-screen behavior with configurable gap
- Backdrop, close button, and escape handling
- Container-scoped mounting by default
- Optional
tiny-engine-coreintegration
Install Dependencies
npm
npm install tiny-draweryarn
yarn add tiny-drawerpnpm
pnpm add tiny-drawerCDN Usage
<link
rel="stylesheet"
href="https://unpkg.com/tiny-drawer/dist/tiny-drawer.min.css"
/>
<script src="https://unpkg.com/tiny-drawer/dist/tiny-drawer.umd.js"></script>
<script>
const drawer = new TinyDrawer.TinyDrawer("#app", {
content: "<div>Hello from CDN</div>"
});
drawer.open();
</script>Vanilla JS Usage
import { TinyDrawer } from "tiny-drawer";
import "tiny-drawer/dist/tiny-drawer.css";
const drawer = new TinyDrawer("#app", {
position: "bottom",
size: "40%",
onScrollFull: {
backDrop: true,
drawer: true,
scrollTarget: ".custom-content"
},
fullScreenGap: 24,
minSize: "20%",
maxSize: "90%",
appendToBody: true,
themePrefix: "acme",
content: `
<div class="custom-content">
<h2>Drawer Title</h2>
<p>This is drawer content.</p>
</div>
`
});
document.querySelector("#openDrawer").addEventListener("click", () => {
drawer.open();
});TypeScript Usage
import { TinyDrawer, type TinyDrawerOptions } from "tiny-drawer";
const options: TinyDrawerOptions = {
position: "right",
size: "40%",
content: "<div>Typed content</div>"
};
const drawer = new TinyDrawer("#panel", options);
drawer.open();SSR and Next.js
tiny-drawer is safe to import in SSR environments because it does not touch the DOM until the browser is available.
For React and Next.js, create or open the drawer from a client-side effect:
"use client";
import { useEffect, useRef } from "react";
import { TinyDrawer } from "tiny-drawer";
export default function Page() {
const drawerRef = useRef<TinyDrawer | null>(null);
useEffect(() => {
drawerRef.current = new TinyDrawer("#app", {
appendToBody: true,
content: "<div>Drawer content</div>"
});
return () => {
drawerRef.current?.destroy();
drawerRef.current = null;
};
}, []);
return <button onClick={() => drawerRef.current?.open()}>Open drawer</button>;
}This keeps hydration safe and avoids server-side DOM access. If you need the drawer mounted inside a specific element, pass parentContainer: "#your-parent" on the client.
API
Constructor
new TinyDrawer(container: string | HTMLElement, options?: TinyDrawerOptions)Methods
| Method | Description |
| ----------------------- | ----------------------------------------------------- |
| open() | Opens the drawer and focuses it. |
| close() | Closes the drawer and restores focus. |
| toggle() | Toggles between open and closed states. |
| destroy() | Removes DOM, listeners, and dispatches destroy hooks. |
| setContent(content) | Replaces drawer content with HTML or an element. |
| setPosition(position) | Changes drawer placement. |
| setSize(size) | Applies a new size value. |
| getSize() | Returns the current size string. |
| isOpen() | Returns current open state. |
Options
| Option | Type | Default | Notes |
| ------------------- | ---------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| position | "top" \| "right" \| "bottom" \| "left" | "bottom" | Defines attachment edge. |
| size | string \| number | "40%" | Default open size. |
| minSize | string \| number | "20%" | Minimum drag size. |
| maxSize | string \| number | "90%" | Maximum drag size. |
| content | string \| HTMLElement | undefined | HTML string, selector, plain text, or element. |
| parentContainer | string \| HTMLElement | undefined | Optional mount target for td-host when you want to mount into a specific parent element. |
| themePrefix | string | undefined | Adds prefixed theme classes such as acme-drawer and acme-drawer--active. |
| backdrop | boolean | true | Enables backdrop layer. |
| closeButton | boolean | true | Renders the close button. |
| closeOnBackdrop | boolean | true | Closes when the backdrop itself is clicked. |
| outSideClickClose | boolean | false | Closes when the user clicks outside the mounted parent/container area. |
| closeOnEscape | boolean | true | Enables keyboard escape closing. |
| draggable | boolean | true | Enables resize handle and drag. |
| onScrollFull | { backDrop, drawer, scrollTarget } | false | backDrop and drawer choose which area can trigger fullscreen behavior, and scrollTarget overrides the watched element. |
| fullScreenGap | string \| number | 0 | Gap preserved from the opposite edge while scroll-full is active. Numbers are treated as pixels. |
| animation | boolean | true | Disables transitions when false. |
| appendToBody | boolean | false | Mounts the drawer to document.body with fixed positioning. |
| lastDragLocation | boolean | true | Keeps the last dragged size after close when enabled. |
| className | string | undefined | Extra class appended to .td-drawer. |
| zIndex | number | 1050 | Base layer stack. |
| ariaLabel | string | "Drawer" | Accessible label for dialog role. |
| onOpen | (instance) => void | undefined | Open callback. |
| onClose | (instance) => void | undefined | Close callback. |
| onDragStart | (instance) => void | undefined | Drag start callback. |
| onDrag | (size, instance) => void | undefined | Drag callback. |
| onDragEnd | (size, instance) => void | undefined | Drag end callback. |
| onDestroy | (instance) => void | undefined | Destroy callback. |
Events
The drawer dispatches custom events from the original parent container:
| Event | Detail |
| ------------------------ | -------------------- |
| tiny-drawer:open | { instance } |
| tiny-drawer:close | { instance } |
| tiny-drawer:drag-start | { instance } |
| tiny-drawer:drag | { instance, size } |
| tiny-drawer:drag-end | { instance, size } |
| tiny-drawer:destroy | { instance } |
Example:
const container = document.querySelector("#app");
container.addEventListener("tiny-drawer:open", (event) => {
console.log(event.detail.instance);
});Position Behavior
bottom: attached to the bottom edge, dragging upward increases height.top: attached to the top edge, dragging downward increases height.left: attached to the left edge, dragging right increases width.right: attached to the right edge, dragging left increases width.
Draggable Behavior
- Uses pointer events for mouse, touch, and pen support.
- Adds
.td-drawer--draggingduring active resize. - Applies
requestAnimationFrameupdates for smoother rendering. - Clamps size between
minSizeandmaxSize. - Accepts
px,%,vh,vw, and numeric pixel values. - Percentage sizes are resolved from the mounted parent container when embedded, and from the viewport when body-mounted.
Scroll Full Behavior
- Enable
onScrollFullto control scroll-full behavior with one object config. onScrollFull.drawermeans scrolling inside the drawer content can trigger fullscreen.onScrollFull.backDropmeans scrolling the parent/background area can trigger fullscreen.- If both are
true, both areas can trigger fullscreen. - If
backDropistrue, the backdrop also getstd-backdrop--scroll-fullwhile fullscreen is active. - The default backdrop target is the mounted parent container for embedded drawers and the page scroll element for body-mounted drawers.
- The default drawer target is the built-in
.td-drawer__innercontent area. - Use
onScrollFull.scrollTargetwith anHTMLElementor selector string to watch a custom scroll container. - Use
fullScreenGapto preserve space from the opposite edge. It acceptspx,%,vh,vw, numbers, and negative values. - Scroll-full sizing is resolved in pixels from the mounted parent or viewport, which keeps the transition smoother in smaller containers.
- When the scroll target returns to the top threshold, the drawer restores its original configured size.
onScrollFull: truestill works and uses the default scroll target automatically.
Example:
const drawer = new TinyDrawer("#drawer", {
position: "bottom",
size: "40%",
onScrollFull: {
backDrop: true,
drawer: true,
scrollTarget: ".drawer-content"
},
fullScreenGap: 24,
content: `
<div class="drawer-content" style="height: 100%; overflow: auto;">
<div style="height: 1200px;">Long content</div>
</div>
`
});Accessibility
role="dialog"aria-modalaria-hiddentoggling- Configurable
aria-label - Escape-key close support
- Focus moves into the drawer on open and returns on close
tiny-engine-core Adapter
import { createTinyEngineDrawer } from "tiny-drawer";
const plugin = createTinyEngineDrawer(core, {
backdrop: true
});
plugin.install();
const drawer = core.drawer?.("#app", {
content: "<div>Installed from tiny-engine-core</div>"
});tiny-engine-core is kept as an optional peer dependency, so the base package stays framework-agnostic.
Demo Scenarios
- Bottom drawer with default 40% height
- Right drawer with 40% width
- Drawer inside a parent card
- Dynamic content update
- Draggable bottom drawer
Open demo/index.html after building to try them locally.
Build Outputs
dist/tiny-drawer.esm.jsdist/tiny-drawer.cjs.jsdist/tiny-drawer.umd.jsdist/tiny-drawer.cssdist/tiny-drawer.min.css- Type declarations in
dist/*.d.ts
Development
npm install
npm run build
npm run lint
npm run testBrowser Support
Modern browsers that support:
- ES2019+
- Pointer Events
- CSS transforms and transitions
- Custom Events
For older browsers, add the relevant polyfills at the application level.
