The Component Workshop: Reflex's 100 Library Components
·
Course overview
Layout
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
Typography
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
Forms
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
Data display
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
Disclosure
·
Tabs
·
Accordion
·
Segmented Control
Overlay
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
Dynamic rendering
·
Cond
·
Match
·
Foreach
·
Auto Scroll
Media
·
Image
·
Audio
·
Video
Tables and data grids
·
Table
·
Data Table
·
Data Editor
Charts
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
Chart parts
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
Other chart libraries
·
Plotly
·
Pyplot
HTML elements
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
Other tools
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
rx.drawer.rootrx.drawer.triggerrx.drawer.overlayrx.drawer.portalrx.drawer.contentrx.drawer.closeOfficial docs
Workshop order
On the mobile shopping screen, tapping 'Filter' should slide a panel up from the bottom where shoppers pick the sort order and stock options.
rx.drawer is a panel that slides in from the edge of the screen. Use it to bring out larger content for a moment, such as a side menu or a picker that rises from the bottom. It can also be dragged closed with a finger or the mouse. Reflex builds this component on the Vaul library.
drawer.root: wraps all the parts. The direction (direction) and the open state go here.drawer.trigger: wraps the element that opens the drawer.drawer.overlay: the translucent layer that covers the page behind while it is open.drawer.portal: renders the content at the very top level of the page so other elements cannot cover it.drawer.content: the panel that slides in.drawer.close: wraps an element that closes the drawer.By default drawer.content is placed to cover the whole screen (position: fixed, all four sides at 0). So to size the panel, release the opposite side with "auto" and set the width or height yourself. For a panel that comes from the left, give it right="auto" and a width.
rx.drawer.root(
rx.drawer.trigger(rx.button("Open Drawer")),
rx.drawer.overlay(z_index="5"),
rx.drawer.portal(
rx.drawer.content(
rx.flex(
rx.drawer.close(rx.box(rx.button("Close"))),
align_items="start",
direction="column",
),
top="auto",
right="auto",
height="100%",
width="20em",
padding="2em",
background_color=rx.color("gray", 2),
)
),
direction="left",
)Change the width and color of a panel that comes from the right (direction="right", left="auto"). Turn modal off and you can click and scroll the page behind while the drawer stays open.
Try the props
title
width
color
modal
rx.drawer.root(
rx.drawer.trigger(rx.button("Open panel")),
rx.drawer.overlay(),
rx.drawer.portal(
rx.drawer.content(
rx.flex(
rx.drawer.title("Settings"),
rx.drawer.description("Drag it to the right or press Esc to close."),
rx.drawer.close(rx.button("Close", size="1")),
direction="column",
align_items="start",
spacing="3",
),
left="auto",
height="100%",
width="20em",
padding="2em",
background_color=rx.color("gray", 3),
),
),
direction="right",
modal=True,
)| Prop (root) | Values | Default | Use |
|---|---|---|---|
direction | "top" "bottom" "left" "right" | "bottom" | Direction it slides in from; also sets the drag-to-close direction |
open · default_open | True / False | - | Drive the open state from State (controlled) · open at first |
modal | True / False | True | With False, the outside can be clicked and scrolled while it is open |
dismissible | True / False | - | With False, dragging, clicking outside and Esc do not close it (use with open) |
handle_only | True / False | - | With True, it can only be dragged by drawer.handle |
snap_points | list of numbers between 0 and 1 or "px" values | - | Heights it stops at (plain Python values only, no State values) |
fade_from_index | int | last point | Snap point index from which the background dims |
close_threshold | 0~1 | - | How far it must be dragged to close |
scroll_lock_timeout | milliseconds | 500 | How long dragging is blocked after scrolling inside the panel |
should_scale_background | True / False | - | Slightly shrink the page behind (needs vaul-drawer-wrapper) |
prevent_scroll_restoration | True / False | True | Prevent restoring the scroll position after it closes |
on_open_change | event | - | Fires on open or close; passes the new open value (bool) |
on_animation_end | event | - | Fires when the open or close animation ends; passes the open value at that moment |
drawer.content has the on_open_auto_focus, on_close_auto_focus, on_escape_key_down, on_pointer_down_outside and on_interact_outside events. The other parts take little more than as_child.
Each direction attaches the panel to a different side, so release the opposite side with "auto". Panels from the top or bottom get a height instead of a width. Building the four drawers is the same work each time, so it is wrapped in one small function.
def drawer_edge_demo(side: str, **position):
return rx.drawer.root(
rx.drawer.trigger(rx.button(side, variant="soft")),
rx.drawer.overlay(),
rx.drawer.portal(
rx.drawer.content(
rx.flex(
rx.drawer.title(f"From the {side}"),
rx.drawer.close(rx.button("Close", size="1")),
direction="column",
align_items="start",
spacing="3",
),
padding="2em",
background_color=rx.color("gray", 2),
**position,
),
),
direction=side,
)
rx.flex(
drawer_edge_demo("left", right="auto", width="16em"),
drawer_edge_demo("right", left="auto", width="16em"),
drawer_edge_demo("top", bottom="auto", height="30%"),
drawer_edge_demo("bottom", top="auto", height="30%"),
spacing="3",
wrap="wrap",
)They are not in the official docs' list of parts, but 0.10 also has drawer.title, drawer.description and drawer.handle. Screen readers read the title and description, and the handle draws a draggable bar at the top of the panel. With handle_only=True the panel can only be dragged by the handle, so it is less likely to close by accident while someone is pressing buttons inside it.
rx.drawer.root(
rx.drawer.trigger(rx.button("Open sheet")),
rx.drawer.overlay(),
rx.drawer.portal(
rx.drawer.content(
rx.flex(
rx.drawer.handle(),
rx.drawer.title("Share this page"),
rx.drawer.description("Drag the handle down to close the sheet."),
rx.flex(
rx.button(rx.icon("link"), "Copy link", variant="soft"),
rx.button(rx.icon("mail"), "Email", variant="soft"),
spacing="3",
),
direction="column",
spacing="3",
width="100%",
),
top="auto",
height="auto",
padding="1.5em",
background_color=rx.color("gray", 2),
border_radius="12px 12px 0 0",
),
),
handle_only=True,
)Bind open to a State value and code can close the drawer when a link is clicked. Wire up on_open_change as well, so open values changed by the trigger button, the Close button or Esc get back to State. Because of modal=False, the page can be scrolled while the drawer is open, and clicking a link closes the drawer and jumps to that section.
class DrawerSidebarState(rx.State):
is_open: bool = False
@rx.event
def set_is_open(self, value: bool):
self.is_open = value
@rx.event
def close_drawer(self):
self.is_open = False
rx.vstack(
rx.drawer.root(
rx.drawer.trigger(rx.button("Open Drawer")),
rx.drawer.overlay(),
rx.drawer.portal(
rx.drawer.content(
rx.flex(
rx.drawer.close(rx.button("Close")),
rx.link("Section 1", href="#drawer-section-1", on_click=DrawerSidebarState.close_drawer),
rx.link("Section 2", href="#drawer-section-2", on_click=DrawerSidebarState.close_drawer),
align_items="start",
direction="column",
spacing="3",
),
right="auto",
height="100%",
width="16em",
padding="2em",
background_color=rx.color("grass", 4),
),
),
open=DrawerSidebarState.is_open,
on_open_change=DrawerSidebarState.set_is_open,
direction="left",
modal=False,
),
rx.section(rx.heading("Section 1", size="5"), id="drawer-section-1", height="160px", padding="1em 0"),
rx.section(rx.heading("Section 2", size="5"), id="drawer-section-2", height="160px", padding="1em 0"),
)A filter panel that rises from the bottom. Values picked with the switch and radio buttons go straight into State, and clicking 'Show results' makes the handler lower the panel with self.open = False. Dragging the panel down keeps the same values.
Sort: Newest
class DrawerFilterState(rx.State):
open: bool = False
in_stock: bool = False
sort: str = "Newest"
@rx.event
def set_open(self, value: bool):
self.open = value
@rx.event
def set_in_stock(self, value: bool):
self.in_stock = value
@rx.event
def set_sort(self, value: str):
self.sort = value
@rx.event
def apply(self):
self.open = False
rx.flex(
rx.text("Sort: ", DrawerFilterState.sort, rx.cond(DrawerFilterState.in_stock, " · in stock only", ""), size="2"),
rx.drawer.root(
rx.drawer.trigger(rx.button(rx.icon("sliders-horizontal"), "Filters", variant="outline")),
rx.drawer.overlay(),
rx.drawer.portal(
rx.drawer.content(
rx.flex(
rx.drawer.handle(),
rx.drawer.title("Filters"),
rx.flex(rx.switch(checked=DrawerFilterState.in_stock, on_change=DrawerFilterState.set_in_stock),
rx.text("In stock only", size="2"), spacing="2", align="center"),
rx.radio(["Newest", "Price: low", "Price: high"], value=DrawerFilterState.sort,
on_change=DrawerFilterState.set_sort, direction="row"),
rx.button("Show results", on_click=DrawerFilterState.apply, width="100%"),
direction="column",
spacing="4",
width="100%",
max_width="480px",
margin="0 auto",
),
top="auto",
height="auto",
padding="1.5em",
background_color=rx.color("gray", 2),
border_radius="12px 12px 0 0",
),
),
open=DrawerFilterState.open,
on_open_change=DrawerFilterState.set_open,
direction="bottom",
),
spacing="3",
align="center",
wrap="wrap",
)Workshop notes
root(direction) > trigger · overlay · portal > content > close. The default direction is bottom.
content is placed to cover the whole screen, so release the opposite side with "auto" and set width or height.
Control it with open + on_open_change and a link or button handler can close the drawer.
modal=False keeps the page behind usable while open; handle_only=True allows dragging only by the handle.
snap_points is fixed at compile time, so it cannot take a State value.
Get news about new lessons
We'll email you when this course gets new lessons or a follow-up course opens.
R
ReflexKR
Learn Python and Reflex by doing. Every example was run and verified on Reflex 0.10.0.
ReflexKR is free, with no banner ads. If it helped you, you can support it with a coffee. ☕ Buy us a coffee
Affiliate disclosure© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Questions & answers
Ask anything about this lesson. Questions from every language version of this page appear together.