Drawer/Offcanvas/Slide-over
#
JavaScript setuplet drawer = () => ({ open: false,
drawerClose: { ["x-on:click"]() { this.open = false } },
drawerOverlay: { ["x-transition:enter"]: "ease-in-out duration-500", ["x-transition:enter-start"]: "opacity-0", ["x-transition:enter-end"]: "opacity-100", ["x-transition:leave"]: "ease-in duration-500", ["x-transition:leave-start"]: "opacity-100", ["x-transition:leave-end"]: "opacity-0", ["x-show"]() { return this.open }, },
drawerContent: { ["x-transition:enter"]: "transform transition ease-in-out duration-500 sm:duration-700", ["x-transition:enter-start"]: "translate-x-full", ["x-transition:enter-end"]: "translate-x-0", ["x-transition:leave"]: "transform transition ease-in-out duration-500 sm:duration-700", ["x-transition:leave-start"]: "translate-x-0", ["x-transition:leave-end"]: "translate-x-full", ["x-on:click.outside"]: "open = false", ["x-show"]() { return this.open }, }})
window.addEventListener("DOMContentLoaded", (event) => { document.querySelectorAll("[x-data='drawer']").forEach(el => { let id = el.id el.setAttribute(`x-on:open-drawer-${id}.window`, "open = true") el.setAttribute("x-show", "open")
document.querySelectorAll(`[data-drawer-id="${id}"]`).forEach(el => { el.setAttribute("x-on:click", `$dispatch('open-drawer-${id}')`) }) })
Alpine.data("drawer", drawer)})
#
How it works<button data-drawer-id="<drawer-id>"></button>
: drawer trigger. Must containdata-drawer-id
.x-data="drawer"
: drawer root element that stores the state. Must haveid
attribute.x-cloak
: Used to hide drawer content before Alpine loads. https://alpinejs.dev/directives/cloakx-bind="drawerOverlay"
: drawer overlay/backdrop.x-bind="drawerPanel"
: Used to place drawer content.x-bind="close"
: Used to close drawer.
<button data-drawer-id="<drawer-id>" type="button"></button>
<div x-data="drawer" x-cloak id="<drawer-id>"> <div x-bind="drawerOverlay"></div>
<div x-bind="drawerContent"> <button x-bind="drawerClose"></button> </div></div>
#
Examples- Preview
- HTML
<button data-drawer-id="drawer-1" type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> Open Drawer</button>
<div x-data="drawer" x-cloak id="drawer-1" class="fixed inset-0 overflow-hidden" aria-labelledby="slide-over-title" role="dialog" aria-modal="true"> <div class="absolute inset-0 overflow-hidden"> <div x-bind="drawerOverlay" class="absolute inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex"> <div x-bind="drawerContent" class="w-screen max-w-md"> <div class="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll"> <div class="px-4 sm:px-6"> <div class="flex items-start justify-between"> <h2 class="text-lg font-medium text-gray-900" id="slide-over-title"> Panel title </h2> <div class="ml-3 h-7 flex items-center"> <button x-bind="drawerClose" type="button" class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> <span class="sr-only">Close panel</span> <!-- Heroicon name: outline/x --> <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div> </div> </div> <div class="mt-6 relative flex-1 px-4 sm:px-6"> <!-- Replace with your content --> <div class="absolute inset-0 px-4 sm:px-6"> <div class="h-full border-2 border-dashed border-gray-200" aria-hidden="true"></div> </div> <!-- /End replace --> </div> </div> </div> </div> </div></div>