---
title: Drawer
description: A panel that slides in from the edge of the screen and can be swiped away.
---

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/badge"
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/drawer"
	"github.com/axadrn/shadcn-templ/v2/components/field"
	"github.com/axadrn/shadcn-templ/v2/components/radiogroup"
)

type drawerDemoTime struct {
	Value       string
	ID          string
	Label       string
	Description string
	Badge       string
}

var drawerDemoTimes = []drawerDemoTime{
	{Value: "asap", ID: "delivery-asap", Label: "Standard delivery", Description: "25–35 min · Driver assigned now", Badge: "Fastest"},
	{Value: "5-00", ID: "delivery-5-00", Label: "5:00 PM – 5:15 PM", Description: "Prep starts at 4:45 PM"},
	{Value: "5-30", ID: "delivery-5-30", Label: "5:30 PM – 5:45 PM", Description: "Good if you're heading home"},
	{Value: "6-00", ID: "delivery-6-00", Label: "6:00 PM – 6:15 PM", Description: "Most popular · High demand"},
	{Value: "6-30", ID: "delivery-6-30", Label: "6:30 PM – 6:45 PM", Description: "Last slot before kitchen closes"},
}

templ DrawerDemo() {
	@drawer.Drawer(drawer.Props{ID: "drawer-demo", SwipeDirection: drawer.SwipeDirectionRight, ShowSwipeHandle: true}) {
		@button.Button(button.Props{Variant: button.VariantSecondary, Attributes: drawer.Trigger(ctx)}) {
			Open Drawer
		}
		@drawer.Content() {
			@drawer.Header() {
				@drawer.Title() {
					Pick a delivery time
				}
				@drawer.Description() {
					We'll prepare your order as soon as possible.
				}
			}
			<div class="flex-1 scroll-fade overflow-y-auto p-4">
				@radiogroup.RadioGroup(radiogroup.Props{Value: "asap", Class: "gap-2"}) {
					for _, time := range drawerDemoTimes {
						@field.Label(field.LabelProps{For: time.ID}) {
							@field.Field(field.Props{Orientation: field.OrientationHorizontal}) {
								@field.Content() {
									@field.Title(field.TitleProps{Class: "flex items-center gap-2"}) {
										{ time.Label }
										if time.Badge != "" {
											@badge.Badge(badge.Props{Variant: badge.VariantSecondary}) {
												{ time.Badge }
											}
										}
									}
									@field.Description() {
										{ time.Description }
									}
								}
								@radiogroup.Item(radiogroup.ItemProps{ID: time.ID, Value: time.Value})
							}
						}
					}
				}
			</div>
			@drawer.Footer() {
				@button.Button(button.Props{ID: "drawer-demo-confirm", Class: "h-[34px]"}) {
					Confirm Delivery Time
				}
				@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Close(ctx)}) {
					Cancel
				}
			}
		}
	}
	<script nonce={ templ.GetNonce(ctx) }>
		(() => {
			// The dialog waits inside its portal <template> until drawer.js
			// lifts it to <body>. The template copy wins over a stale portaled
			// copy from a previous htmx swap; attributes and listeners set on
			// it survive the lift (same node).
			const drawerDemo = () => {
				for (const tpl of document.querySelectorAll("template[data-tui-drawer-portal]")) {
					const content = tpl.content.getElementById("drawer-demo");
					if (content) return content;
				}
				return document.getElementById("drawer-demo");
			};

			// useIsMobile pendant (hooks/use-mobile.ts): swipeSwipeDirection is
			// down and the swipe handle shows below the md breakpoint, right
			// and no handle from md up. Demo-only behavior, so it lives here
			// instead of the component API. The direction attributes live on
			// the popup (Base UI Drawer.Popup).
			const mq = window.matchMedia("(max-width: 767px)");
			const apply = () => {
				const dialog = drawerDemo();
				const popup = dialog?.querySelector('[data-tui-drawer-popup]');
				if (!popup) return;
				popup.setAttribute("data-swipe-direction", mq.matches ? "down" : "right");
				popup.setAttribute("data-swipe-axis", mq.matches ? "y" : "x");
				const handle = popup.querySelector('[data-slot="drawer-swipe-handle"]');
				// Inline display, because the handle's flex class would win
				// over the [hidden] UA rule.
				if (handle) handle.style.display = mq.matches ? "" : "none";
			};
			apply();
			mq.addEventListener("change", apply);

			// handleConfirm pendant: close the drawer, then confirm the
			// selected slot via toast.
			const labels = {
				"asap": "Standard delivery",
				"5-00": "5:00 PM – 5:15 PM",
				"5-30": "5:30 PM – 5:45 PM",
				"6-00": "6:00 PM – 6:15 PM",
				"6-30": "6:30 PM – 6:45 PM",
			};
			const confirm = drawerDemo()?.querySelector("#drawer-demo-confirm");
			confirm?.addEventListener("click", () => {
				const dialog = drawerDemo();
				const selected = dialog?.querySelector("input[data-tui-radio-input]:checked");
				if (!selected || !(selected.value in labels)) return;
				window.tui.drawer.close(dialog);
				window.tui.toast.add({
					title: "Delivery time confirmed",
					description: labels[selected.value],
				});
			});
		})();
	</script>
}
```

## Installation

<CodeTabs>

<TabsList>
  <TabsTrigger value="cli">Command</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">

```bash
shadcn-templ add drawer
```

</TabsContent>

<TabsContent value="manual">

<Steps className="mb-0 pt-2">

<Step>Copy and paste the following code into your project.</Step>

<ComponentSource name="drawer" title="components/drawer/drawer.templ" />

<ComponentSource name="drawer" title="components/drawer/drawer.js" />

Component scripts are loaded through the shared script bundle, see [JavaScript](/docs/installation#javascript).

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</CodeTabs>

## Usage

```go showLineNumbers
import "github.com/axadrn/shadcn-templ/v2/components/drawer"
```

```templ showLineNumbers
@drawer.Drawer() {
	@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Trigger(ctx)}) {
		Open
	}
	@drawer.Content() {
		@drawer.Header() {
			@drawer.Title() {
				Are you absolutely sure?
			}
			@drawer.Description() {
				This action cannot be undone.
			}
		}
		<div class="p-4">
			<!-- Content here -->
		</div>
		@drawer.Footer() {
			@button.Button() {
				Submit
			}
			@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Close(ctx)}) {
				Cancel
			}
		}
	}
}
```

## Composition

Use the following composition to build a `Drawer`:

```text
drawer.Drawer
├── drawer.Trigger
└── drawer.Content
    ├── drawer.Header
    │   ├── drawer.Title
    │   └── drawer.Description
    └── drawer.Footer
```

`drawer.Content` composes the portal, overlay, viewport, and popup: the script portals the panel to `body`, the native dialog plays the viewport role, and a dedicated overlay element renders behind the panel. For lower-level control, `drawer.SwipeHandle` is also exported.

## Custom Sizes

A vertical drawer sizes itself to its content and is capped at `calc(100dvh - 6rem)` by default. A side drawer spans `75%` of the viewport width, or `24rem` on larger screens.

To customize the height of a vertical drawer, use the `h-*` and `max-h-*` utilities via `Class` on `drawer.Content`.

```templ
@drawer.Content(drawer.ContentProps{Class: "h-[50vh]"})
```

To customize the width of a side drawer, use the `w-*` and `max-w-*` utilities via `Class` on `drawer.Content`.

```templ
@drawer.Content(drawer.ContentProps{Class: "w-96"})
```

When the same component renders in multiple directions, scope an override to one axis using the `data-[swipe-axis=*]` variants.

```templ
@drawer.Content(drawer.ContentProps{Class: "data-[swipe-axis=y]:max-h-[50vh] data-[swipe-axis=x]:w-96"})
```

To make a region of the drawer scrollable, make the scroll container a flex item. Avoid `h-full`, which does not resolve inside a content-sized drawer.

```templ
@drawer.Content() {
	@drawer.Header() {
		...
	}
	<div class="flex-1 overflow-y-auto p-4">
		<!-- Scrollable content -->
	</div>
	@drawer.Footer() {
		...
	}
}
```

## Styling

The drawer exposes CSS variables for style-level customization. Set the sizing variables via `Class` on `drawer.Content`. Set the overlay variable on `[data-slot=drawer-overlay]` in your CSS.

| Variable                       | Default                | Description                                                             |
| ------------------------------ | ---------------------- | ----------------------------------------------------------------------- |
| `--drawer-inset`               | `0px`                  | Floats the drawer from the viewport edges.                              |
| `--drawer-bleed-background`    | `var(--color-popover)` | Fills the gap behind the drawer on swipe overshoot.                     |
| `--drawer-overlay-min-opacity` | `0`                    | Minimum overlay opacity. Defaults to `0.5` when snap points are active. |

The drawer also sets data attributes you can target with variants such as `data-[swipe-direction=down]:` on `drawer.Content`, or `group-data-[swipe-axis=y]/drawer-popup:` on its descendants.

| Attribute                 | Values                        | Set when                              |
| ------------------------- | ----------------------------- | ------------------------------------- |
| `data-swipe-direction`    | `up`, `right`, `down`, `left` | Always.                               |
| `data-swipe-axis`         | `x`, `y`                      | Always.                               |
| `data-snap-points`        | Present                       | The drawer has snap points.           |
| `data-expanded`           | Present                       | The drawer is at the full snap point. |
| `data-swiping`            | Present                       | A swipe is in progress.               |
| `data-nested-drawer-open` | Present                       | A nested drawer is open on top.       |

## Position

Use the `SwipeDirection` prop on `drawer.Drawer` to set the side of the drawer.

Available options are `SwipeDirectionUp`, `SwipeDirectionRight`, `SwipeDirectionDown`, and `SwipeDirectionLeft`. The default is `SwipeDirectionDown`.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/drawer"
)

templ DrawerSides() {
	@drawer.Drawer(drawer.Props{SwipeDirection: drawer.SwipeDirectionLeft}) {
		@button.Button(button.Props{Variant: button.VariantSecondary, Attributes: drawer.Trigger(ctx)}) {
			Open Left Drawer
		}
		@drawer.Content() {
			@drawer.Header() {
				@drawer.Title() {
					Move Goal
				}
				@drawer.Description() {
					Set your daily activity goal.
				}
			}
			<div class="flex-1 p-4">
				<div class="size-full rounded-2xl bg-muted"></div>
			</div>
			@drawer.Footer() {
				@button.Button(button.Props{Attributes: drawer.Close(ctx)}) {
					Close
				}
			}
		}
	}
}
```

## Swipe Handle

Use `ShowSwipeHandle` on `drawer.Drawer` to render a swipe handle.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/drawer"
)

templ DrawerSwipeHandle() {
	@drawer.Drawer(drawer.Props{ShowSwipeHandle: true}) {
		@button.Button(button.Props{Variant: button.VariantSecondary, Attributes: drawer.Trigger(ctx)}) {
			Open Drawer
		}
		@drawer.Content() {
			@drawer.Header() {
				@drawer.Title() {
					Drawer
				}
				@drawer.Description() {
					Drawer with a swipe handle.
				}
			}
			<div class="flex-1 p-4">
				<div class="rounded-2xl bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full"></div>
			</div>
			@drawer.Footer() {
				@button.Button(button.Props{Attributes: drawer.Close(ctx)}) {
					Close
				}
			}
		}
	}
}
```

## Nested

Open drawers from inside another drawer. Parent drawers stay mounted and stack behind the frontmost drawer.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/drawer"
)

templ DrawerNested() {
	@drawer.Drawer(drawer.Props{ID: "drawer-nested-1", SwipeDirection: drawer.SwipeDirectionRight, ShowSwipeHandle: true}) {
		@button.Button(button.Props{Variant: button.VariantSecondary, Attributes: drawer.Trigger(ctx)}) {
			Open Drawer
		}
		@drawer.Content() {
			@drawer.Header() {
				@drawer.Title() {
					Drawer
				}
				@drawer.Description() {
					Open another drawer from the same direction.
				}
			}
			<div class="flex-1 p-4">
				<div class="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:aspect-video group-data-[swipe-axis=y]/drawer-popup:w-full"></div>
			</div>
			@drawer.Footer() {
				@drawer.Drawer(drawer.Props{ID: "drawer-nested-2", SwipeDirection: drawer.SwipeDirectionRight, ShowSwipeHandle: true}) {
					@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Trigger(ctx)}) {
						Open Nested Drawer
					}
					@drawer.Content() {
						@drawer.Header() {
							@drawer.Title() {
								Nested Drawer
							}
							@drawer.Description() {
								The parent drawer stays mounted behind this one.
							}
						}
						<div class="flex-1 p-4">
							<div class="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:aspect-video group-data-[swipe-axis=y]/drawer-popup:w-full"></div>
						</div>
						@drawer.Footer() {
							@drawer.Drawer(drawer.Props{ID: "drawer-nested-3", SwipeDirection: drawer.SwipeDirectionRight, ShowSwipeHandle: true}) {
								@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Trigger(ctx)}) {
									Open Third Drawer
								}
								@drawer.Content() {
									@drawer.Header() {
										@drawer.Title() {
											Third Drawer
										}
										@drawer.Description() {
											Two drawers are stacked behind this one.
										}
									}
									<div class="flex-1 p-4">
										<div class="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:aspect-video group-data-[swipe-axis=y]/drawer-popup:w-full"></div>
									</div>
									@drawer.Footer() {
										@drawer.Drawer(drawer.Props{ID: "drawer-nested-4", SwipeDirection: drawer.SwipeDirectionRight, ShowSwipeHandle: true}) {
											@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Trigger(ctx)}) {
												Open Fourth Drawer
											}
											@drawer.Content() {
												@drawer.Header() {
													@drawer.Title() {
														Fourth Drawer
													}
													@drawer.Description() {
														This is the frontmost drawer in the stack.
													}
												}
												<div class="flex-1 p-4">
													<div class="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:aspect-video group-data-[swipe-axis=y]/drawer-popup:w-full"></div>
												</div>
												@drawer.Footer() {
													@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Close(ctx)}) {
														Close
													}
												}
											}
										}
										@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Close(ctx)}) {
											Close
										}
									}
								}
							}
							@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Close(ctx)}) {
								Close
							}
						}
					}
				}
				@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Close(ctx)}) {
					Close
				}
			}
		}
	}
	<script nonce={ templ.GetNonce(ctx) }>
		(() => {
			// useIsMobile pendant (hooks/use-mobile.ts), like the drawer-demo:
			// swipeSwipeDirection is down and the swipe handle shows below the md
			// breakpoint, right and no handle from md up. The dialogs wait in
			// nested portal <template>s until drawer.js lifts them, so the
			// lookup searches template contents recursively; attributes set
			// on the nodes survive the lift (same nodes).
			const findDialog = (root, id) => {
				const direct = root.getElementById?.(id) || root.querySelector?.("#" + CSS.escape(id));
				if (direct) return direct;
				for (const tpl of root.querySelectorAll("template[data-tui-drawer-portal]")) {
					const found = findDialog(tpl.content, id);
					if (found) return found;
				}
				return null;
			};

			const ids = ["drawer-nested-1", "drawer-nested-2", "drawer-nested-3", "drawer-nested-4"];
			const mq = window.matchMedia("(max-width: 767px)");
			const apply = () => {
				for (const id of ids) {
					const dialog = findDialog(document, id);
					const popup = dialog?.querySelector('[data-tui-drawer-popup]');
					if (!popup) continue;
					popup.setAttribute("data-swipe-direction", mq.matches ? "down" : "right");
					popup.setAttribute("data-swipe-axis", mq.matches ? "y" : "x");
					const handle = popup.querySelector('[data-slot="drawer-swipe-handle"]');
					// Inline display, because the handle's flex class would
					// win over the [hidden] UA rule.
					if (handle) handle.style.display = mq.matches ? "" : "none";
				}
			};
			apply();
			mq.addEventListener("change", apply);
		})();
	</script>
}
```

## Non Modal

Set `DisableModal` to allow interaction with the rest of the page while the drawer is open. Combine with `DisableDismissible` to prevent the drawer from closing on outside presses.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/drawer"
)

templ DrawerNonModal() {
	@drawer.Drawer(drawer.Props{DisableModal: true, DisableDismissible: true, SwipeDirection: drawer.SwipeDirectionRight}) {
		@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Trigger(ctx)}) {
			Non Modal
		}
		@drawer.Content() {
			@drawer.Header() {
				@drawer.Title() {
					Non Modal Drawer
				}
			}
			<div class="flex-1 p-4">
				<div class="rounded-2xl bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full"></div>
			</div>
			@drawer.Footer() {
				@button.Button(button.Props{Attributes: drawer.Close(ctx)}) {
					Close
				}
			}
		}
	}
}
```

## Snap Points

Use `SnapPoints` to snap a drawer to preset heights. Numbers between `0` and `1` represent fractions of the viewport. Numbers greater than `1` are treated as pixel values. String values support `px` and `rem` units. Snap points apply to vertical drawers.

Track and control the active snap point with `window.tui.drawer.getSnapPoint(id)` and `window.tui.drawer.setSnapPoint(id, value)`. At the full snap point, the drawer gets a `data-expanded` attribute you can style with the `data-expanded:` variant.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/drawer"
)

templ DrawerSnapPoints() {
	@drawer.Drawer(drawer.Props{SnapPoints: []any{"31rem", 1}, ShowSwipeHandle: true}) {
		@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Trigger(ctx)}) {
			Open Snap Drawer
		}
		@drawer.Content() {
			@drawer.Header() {
				@drawer.Title() {
					Snap points
				}
				@drawer.Description() {
					Drag the drawer to snap between a compact peek and a near full-height view.
				}
			}
			<div class="flex-1 p-4">
				<div class="rounded-2xl bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full"></div>
			</div>
			@drawer.Footer() {
				@button.Button(button.Props{Attributes: drawer.Close(ctx)}) {
					Close
				}
			}
		}
	}
}
```

## Responsive

You can combine the `Dialog` and `Drawer` components to create a responsive dialog. This renders a `Dialog` component on desktop and a `Drawer` on mobile.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/dialog"
	"github.com/axadrn/shadcn-templ/v2/components/drawer"
	"github.com/axadrn/shadcn-templ/v2/components/input"
	"github.com/axadrn/shadcn-templ/v2/components/label"
	"github.com/axadrn/shadcn-templ/v2/utils"
)

// DrawerDialog is the responsive dialog: a Dialog on desktop and a Drawer on
// mobile (the reference's useMediaQuery("(min-width: 768px)")). Both are
// SSR'd; the demo-local script below shows one and hides the other.
templ DrawerDialog() {
	<div data-tui-drawer-dialog-desktop hidden>
		@dialog.Dialog(dialog.Props{ID: "drawer-dialog-desktop"}) {
			@button.Button(button.Props{Variant: button.VariantOutline, Attributes: dialog.Trigger(ctx)}) {
				Edit Profile
			}
			@dialog.Content(dialog.ContentProps{Class: "sm:max-w-[425px]"}) {
				@dialog.Header() {
					@dialog.Title() {
						Edit profile
					}
					@dialog.Description() {
						Make changes to your profile here. Click save when you're done.
					}
				}
				@drawerDialogProfileForm("")
			}
		}
	</div>
	<div data-tui-drawer-dialog-mobile hidden>
		@drawer.Drawer(drawer.Props{ID: "drawer-dialog-mobile"}) {
			@button.Button(button.Props{Variant: button.VariantOutline, Attributes: drawer.Trigger(ctx)}) {
				Edit Profile
			}
			@drawer.Content() {
				@drawer.Header(drawer.HeaderProps{Class: "text-left"}) {
					@drawer.Title() {
						Edit profile
					}
					@drawer.Description() {
						Make changes to your profile here. Click save when you're done.
					}
				}
				@drawerDialogProfileForm("p-4")
			}
		}
	</div>
	<script nonce={ templ.GetNonce(ctx) }>
		(() => {
			// useMediaQuery pendant: show the Dialog markup on desktop and
			// the Drawer on mobile. Both are server-rendered; only one set of
			// triggers is visible at a time. Demo-local behavior, not a
			// component API.
			const mq = window.matchMedia("(min-width: 768px)");
			const apply = () => {
				document.querySelectorAll("[data-tui-drawer-dialog-desktop]").forEach((el) => {
					el.hidden = !mq.matches;
				});
				document.querySelectorAll("[data-tui-drawer-dialog-mobile]").forEach((el) => {
					el.hidden = mq.matches;
				});
			};
			apply();
			mq.addEventListener("change", apply);
		})();
	</script>
}

templ drawerDialogProfileForm(class string) {
	<form class={ utils.CN("grid items-start gap-6", class) }>
		<div class="grid gap-3">
			@label.Label(label.Props{For: "drawer-dialog-email"}) {
				Email
			}
			@input.Input(input.Props{ID: "drawer-dialog-email", Type: "email", Value: "axel@example.com"})
		</div>
		<div class="grid gap-3">
			@label.Label(label.Props{For: "drawer-dialog-username"}) {
				Username
			}
			@input.Input(input.Props{ID: "drawer-dialog-username", Value: "@axadrn"})
		</div>
		@button.Button(button.Props{Type: button.TypeSubmit}) {
			Save changes
		}
	</form>
}
```

## API Reference

### Drawer

The `drawer.Drawer` component is the root, it carries the id and options that link trigger and content.

| Prop                     | Type                                                     | Default         |
| ------------------------ | -------------------------------------------------------- | --------------- |
| `Open`                   | `bool`                                                   | `false`         |
| `DisableDismissible`       | `bool`                                                   | `false`         |
| `DisableModal`           | `bool`                                                   | `false`         |
| `SwipeDirection`              | `SwipeDirectionDown \| SwipeDirectionUp \| SwipeDirectionLeft \| SwipeDirectionRight` | `SwipeDirectionDown` |
| `ShowSwipeHandle`        | `bool`                                                   | `false`         |
| `SnapPoints`             | `[]any`                                                  | -               |
| `SnapToSequentialPoints` | `bool`                                                   | `false`         |

### DrawerTrigger

`drawer.Trigger(ctx)` returns the attributes that turn any element into the drawer trigger, `drawer.TriggerFor(id)` targets a drawer outside the current root. `drawer.Close(ctx)` and `drawer.CloseFor(id)` close it.

### DrawerContent

The `drawer.Content` component is the sliding panel.

| Prop    | Type     | Default |
| ------- | -------- | ------- |
| `Class` | `string` | -       |

### DrawerSwipeHandle

The `drawer.SwipeHandle` component is the drag handle bar. `drawer.Content` renders it automatically when `ShowSwipeHandle` is set on the root.

| Prop    | Type     | Default |
| ------- | -------- | ------- |
| `Class` | `string` | -       |

### DrawerHeader

The `drawer.Header` component wraps the title and description.

| Prop    | Type     | Default |
| ------- | -------- | ------- |
| `Class` | `string` | -       |

### DrawerFooter

The `drawer.Footer` component holds actions at the bottom.

| Prop    | Type     | Default |
| ------- | -------- | ------- |
| `Class` | `string` | -       |

### DrawerTitle

The `drawer.Title` component renders the accessible drawer title.

| Prop    | Type     | Default |
| ------- | -------- | ------- |
| `Class` | `string` | -       |

### DrawerDescription

The `drawer.Description` component renders the accessible drawer description.

| Prop    | Type     | Default |
| ------- | -------- | ------- |
| `Class` | `string` | -       |
