Getting Started
Confirmation
The standard pattern. Default drag-handle + swipe-down to dismiss.
import { useState } from "react"
import {
Body2,
Drawer,
Flex,
PrimaryButton,
SecondaryButton,
Title3,
} from "@appboxo/ui-kit"
import { PreviewLayout } from "./_section"
export function DrawerConfirmPreview() {
const [open, setOpen] = useState(false)
return (
<PreviewLayout>
<PrimaryButton text="Confirm purchase" onClick={() => setOpen(true)} />
<Drawer visible={open} close={() => setOpen(false)}>
<Flex vertical gap={16} style={{ padding: 24 }}>
<Title3>Redeem 1,200 points?</Title3>
<Body2 color="text-3">
You'll get a $12 voucher emailed within 5 minutes.
</Body2>
<Flex vertical={false} gap={8}>
<PrimaryButton text="Confirm" onClick={() => setOpen(false)} />
<SecondaryButton text="Cancel" onClick={() => setOpen(false)} />
</Flex>
</Flex>
</Drawer>
</PreviewLayout>
)
}
Details
Use for read-only flows with mixed content (key-value pairs, lists, CTAs).
import { useState } from "react"
import {
Body1,
Body2,
Drawer,
Flex,
PrimaryButton,
SecondaryButton,
Title3,
} from "@appboxo/ui-kit"
import { PreviewLayout } from "./_section"
export function DrawerDetailsPreview() {
const [open, setOpen] = useState(false)
return (
<PreviewLayout>
<SecondaryButton
text="Open ticket details"
onClick={() => setOpen(true)}
/>
<Drawer visible={open} close={() => setOpen(false)}>
<Flex vertical gap={16} style={{ padding: 24 }}>
<Title3>Lounge pass · KUL · Plaza Premium</Title3>
<Flex vertical gap={6}>
<Body1 weight="semibold">Valid until</Body1>
<Body2 color="text-3">14 Aug 2026 — 23:00 KUL local time</Body2>
</Flex>
<Flex vertical gap={6}>
<Body1 weight="semibold">Booking ID</Body1>
<Body2 color="text-3">BKG-7F3A-92EE-114B</Body2>
</Flex>
<PrimaryButton text="Done" onClick={() => setOpen(false)} />
</Flex>
</Drawer>
</PreviewLayout>
)
}
Without drag handle
Pass hideControl to hide the grab indicator at the top. Use when you want users to dismiss only via the explicit close button.
import { useState } from "react"
import {
Body2,
Drawer,
Flex,
PrimaryButton,
SecondaryButton,
Title3,
} from "@appboxo/ui-kit"
import { PreviewLayout } from "./_section"
export function DrawerNoHandlePreview() {
const [open, setOpen] = useState(false)
return (
<PreviewLayout>
<SecondaryButton
text="Open without handle"
onClick={() => setOpen(true)}
/>
<Drawer hideControl visible={open} close={() => setOpen(false)}>
<Flex vertical gap={16} style={{ padding: 24 }}>
<Title3>No drag handle</Title3>
<Body2 color="text-3">
Use this variant when the user should dismiss via an explicit
button only. Swipe-down still works.
</Body2>
<PrimaryButton text="Close" onClick={() => setOpen(false)} />
</Flex>
</Drawer>
</PreviewLayout>
)
}
Installation
pnpm add @appboxo/ui-kitBuild mini-apps with Boxo
Freedom UI Kit is the design system behind partner mini-apps shipping on the Boxo platform.
Learn more about Boxo