Components
Browse the building blocks of every Rahhal mini-app.
58 components across the platform
Colors & radius
Every design token from `@rahhal/themes`. Stored as HSL channels (so Tailwind opacity modifiers work) AND surfaced as `#RRGGBB` hex for cross-platform consumers (iOS, Android, slides, Figma). Click any hex chip to copy it. Try toggling theme via the cog — every swatch flips live.
Typography
Font families (sans · arabic · mono) with web stacks plus iOS/Android equivalents, type scale (rem + px), and weight ramp. Bilingual EN/AR samples live alongside each other so designers can review both at once. Click any chip to copy.
Icons & brand marks
Download official Rahhal icons in PNG and SVG, with light and dark variants, React usage, and guidance for Lucide UI icons and social brand marks.
RahhalIcon
Cross-platform icon renderer. Parses the same prefixed catalog strings the iOS shell understands (`url:…`, `emoji:…`, `system:…`, `resource:…`, plus bare URLs/emojis) so a CMS-authored value paints identically on every client. Falls back to a muted square when the value is empty; `system:` and `resource:` render as debug tokens on the web because there's no SF Symbols / iOS bundle equivalent.
Button
Primary actionable element. Six variants (default, destructive, outline, secondary, ghost, link) and four sizes. Use `asChild` to keep button styling on a router-aware link.
BackButton
Small ghost capsule with a leading chevron — the standard 'go back' affordance for nested pages. Defaults to `window.history.back()`; pass `onClick` to plug in router-aware navigation. The chevron rotates automatically under RTL.
PasteButton
Icon button that reads the system clipboard and forwards the text to a callback. Defaults to a clipboard glyph; pair with a string-transform helper at the call site (URL extractor, code parser, normaliser).
Toggle · ToggleGroup
On/off button (`<Toggle>`) and grouped toggle sets (`<ToggleGroup>`). Use for formatting toolbars, view-mode switches, and any compact two-state control. `ToggleGroup` accepts `type="single"` for radio-style behavior or `type="multiple"` for checkbox-style.
Input & Textarea
Native `<input>` and `<textarea>` wrappers themed via design tokens. Pair with `<Label htmlFor>` for accessible click-the-text targets.
SearchBar
Capsule-shaped search input with leading magnifier, optional clear button, optional trailing slot (e.g. `⌘K` hint), and Enter-to-submit. Wrapped in a `<form role="search">` so iOS shows the on-screen "Search" key. Inputs in this design system are normally rectangular — search is the one intentional capsule exception, matching Apple's HIG and iOS Search.
FilterChip
Toggleable filter pill. Two interaction modes that compose: `active` + `onToggle` for multi-select category chips, `onRemove` for "applied filter" chips that show what's currently filtering the list. Optional `leading` icon and `count` badge. Use over `<Tabs>` when you have 3+ values, multi-select, or variable counts.
FileDropzone
Drop-or-browse file picker with native HTML5 drag-and-drop, click-to-select fallback, and built-in `accept` / `maxSize` / `maxFiles` validation. Always controlled — the parent owns the file list.
Switch · Checkbox · Radio
Boolean and small-set selectors. `<Switch>` for on/off settings, `<Checkbox>` for individual toggles, `<RadioGroup>` for mutually-exclusive choices.
Slider
Value picker with a draggable thumb. Built on Radix Slider. Pass a single-element array for a normal slider or a two-element array for a range slider.
Combobox
Searchable single-select picker. Composes `<Popover>` + `<Command>` + `<Button>` into the canonical shadcn pattern. Use for country / city / airport / category pickers where `<Select>` becomes unwieldy past ~10 options. Always controlled.
Calendar & DatePicker
Themed `<Calendar>` (react-day-picker) plus the canonical `<DatePicker>` recipe (Calendar inside a Popover). Single date and range modes share the same trigger. RTL-safe — the day grid mirrors automatically, and chevrons rotate via `rtl:rotate-180`.
Form (react-hook-form)
Validated form composition built on react-hook-form. `<FormField>` wires a controller into descendant `<FormLabel>` / `<FormControl>` / `<FormDescription>` / `<FormMessage>` parts via shared aria attributes — labels point to inputs, errors are announced, and focus rings show on the right element automatically.
Section
Page-section building block with consistent typography, spacing, and a header (title + description + optional action). Pair with `Card`, `List`, or `Table` for the surface; `Section` only handles rhythm and labeling so layouts stay uniform across mini-apps.
Card
Surface container for grouped content. Compose with `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`. Each piece is independent so partial layouts are fine.
List
Apple-style inset grouped list. Each `ListGroup` is a rounded card with hairline dividers between rows, optional uppercase header above and helper-text footer below — same model as iOS Settings. Compose `ListItem` rows with `leading` (icon/avatar), `title`, `description`, and `trailing` (value, chevron, switch). Use `variant="plain"` to embed inside another card.
PlaceCard
Compact, mobile-first card for a single geographic place. Name, optional address, and the coordinate readout stack vertically and truncate — never scroll horizontally. The title is adaptive: it shows `name`, else `address`, else the formatted coordinates, so a nameless waypoint still renders cleanly. Pass an `<Input/>` as `name` for inline editing; use `leading`/`trailing` for an icon and a checkbox/switch.
Table
Themed `<table>` family wrapped in a rounded card surface. Use for tabular data — invoices, bookings, comparisons. For settings-style or single-column lists, prefer `List` instead.
Kanban board
Column-based task board layout primitive. Provides the board frame, columns (with title / count / accent dot / action), and cards (with title / description / leading / trailing / meta slots). Layout only — apps wire drag-and-drop with their own DnD library (e.g. `@dnd-kit/core`). On small screens columns stack and snap-scroll horizontally so swipes feel native.
Map
Declarative wrapper around MapLibre GL JS. Compose `MapMarker`, `MapPolyline`, `MapPolygon`, `MapCircle`, and `MapCluster` as children — geometry inherits the platform's blue primary by default. Free OpenFreeMap tiles light/dark; switch via the `style` prop. RTL Arabic labels render correctly out of the box. The web component complements iOS MapKit and Android Maps SDK in the native Rahhal apps.
Carousel
Horizontal (or vertical) scroller built on Embla Carousel. Used for photo galleries, onboarding, and any "swipe through a row" UX. Detects `[dir="rtl"]` automatically and forwards the right axis direction to Embla.
Collapsible
Minimal disclosure primitive (one toggle, one panel). Re-exports of Radix Collapsible. For multiple sections, use `<Accordion>` instead.
Layout primitives
Three small but essential layout primitives shipped together: `<AspectRatio>` (lock a ratio for media), `<ScrollArea>` (themed custom scrollbar), and `<Separator>` (semantic divider). They show up everywhere — they get one shared catalog page so the sidebar stays focused on richer components.
Country facts
Drop-in country UI from `@rahhal/countries/react` — `CountryFlag`, `CountryCard`, `CountryFacts`, and `CountryDetail`. Each is data-in or self-fetching (by `name`/`code`) and accepts a `labels` prop so the host app localizes. Composed from `@rahhal/ui`, so it matches the app theme.
Badge
Inline label for status, counts, tags. Four variants (default, secondary, destructive, outline).
Avatar · Skeleton · Progress
Compact identity + loading-state primitives.
Alert
Inline status callout. Pair with a leading icon for instant recognition.
Callout
Inline note / hint / warning / important block. Lighter than an Alert — used inside page bodies for disclaimers, agent-targeted hints, deprecation notes, and similar asides.
Toaster (sonner)
One `<Toaster />` mount + the imperative `toast(…)` helper. Themes follow `useTheme()` automatically.
Spinner
Small indeterminate loading indicator. Use inline beside text, inside busy buttons, or centered in a wait block. Stroke is `currentColor` so it inherits text color, and the size scale matches Lucide icons.
Rating
Star rating with read-only display + interactive input modes. Half-star increments by default, keyboard support (Arrow / Home / End), and an `aria-label` describing the current value. Always renders LTR so 1-star sits at the start edge in both reading directions.
EmptyState
Recurring "nothing here yet" surface. Drop into any list, table, or section that can be empty — icon, title, description, and an optional action keep the spacing rhythm consistent across mini-apps.
JsonViewer
Read-only, syntax-highlighted JSON block. Use for admin payload inspectors, debug panels, webhook viewers — anywhere an operator skims, copies, or downloads a structured payload. Theme tokens are wired through CSS variables so it follows light / dark automatically.
Dialog · Sheet
Centered modal vs. side sheet. Use a dialog for short focused interactions; use a sheet for navigation drawers, filters, or contextual editors.
AlertDialog
Interrupting confirmation dialog for destructive or unrecoverable actions. The user must explicitly confirm or cancel — clicking outside does not dismiss. Built on Radix Alert Dialog.
Drawer
Bottom-sheet style modal that slides up from the edge of the viewport. Built on `vaul` for touch-friendly drag-to-dismiss. Use for mobile-first flows where a centered `<Dialog>` would feel heavy. For desktop side panels, prefer `<Sheet>`.
Popover
Floating panel anchored to a trigger. Non-modal — clicking outside dismisses. Built on Radix Popover. Use for date pickers, filter panels, color pickers, settings popouts, or any disclosure that doesn't need to block the page.
HoverCard
Small floating preview shown on hover or focus. Use for profile cards, link previews, or anywhere a tooltip would be too small for the content. Built on Radix Hover Card.
Command palette
Searchable list / command palette built on `cmdk`. Use inline (`<Command>`) for typeahead pickers, or wrapped in `<CommandDialog>` for a global cmd-K launcher. Items are scored client-side as the user types.
Dropdown · Select · Tooltip
Floating triggers and selectors. All three flip correctly under RTL.
Tabs · Accordion
Disclose grouped content. Use tabs for parallel views, accordion for sequential reveal.
NavBar
Opinionated top navigation built on `<AppHeader>`. Brand on the start, typed links centered on `md+`, actions on the end. Router-agnostic via a `renderLink` render prop so apps can supply their own `<NavLink>` / `<Link>`.
Sidebar
Layout primitives for the catalog-style left rail every Rahhal mini-app uses: a glassy floating column with a sticky filter / brand area, a scrolling body, and collapsible sections. Pair `<AppSidebar>` with `<AppSidebarHeader>`, `<AppSidebarBody>`, `<AppSidebarSection>`, `<AppSidebarItem>` (and optional `<AppSidebarFooter>`). The chrome is layout-only — apps supply data, routing, and filtering. For mobile, render the same content inside a `<Sheet>` and add a hamburger button to `<AppHeader>`.
Breadcrumb
Hierarchical navigation trail. Compose `<Breadcrumb>` → `<BreadcrumbList>` → `<BreadcrumbItem>` (containing `<BreadcrumbLink>` or `<BreadcrumbPage>`) with `<BreadcrumbSeparator>` between siblings. The default chevron flips under RTL.
Pagination
Accessible page navigator. Compose `<Pagination>` → `<PaginationContent>` → `<PaginationItem>` (with `<PaginationLink>`, `<PaginationPrevious>`, `<PaginationNext>`, or `<PaginationEllipsis>` inside). Previous/Next chevrons rotate under RTL.
Stepper
Multi-step progress indicator. Horizontal (default) or vertical orientation; status (`complete`, `current`, `upcoming`) is derived per-step from `activeStep`. Used for wizards and onboarding flows.
WizardHeader
Sticky per-step header for multi-step flows inside a native host (`?host=…`), where the web hides its own AppHeader/AppFooter. Lays out a back control at the start, a centered title, and an optional action at the end — plus a slim segmented progress bar. Back navigation is delegated via `onBack` (omit it on the first step); top safe-area padding clears the iOS notch. RTL-aware.
Embedded mode
Detection helper for when a mini-app is loaded inside an iOS / Android / desktop host container. The host signals via `?host=<value>`; the web persists it in sessionStorage and exposes it to layouts via `useIsEmbedded()` so chrome (`<AppHeader>`, `<AppFooter>`) can be hidden in favor of the host's native nav.
Footer
Bottom chrome for a Rahhal mini-app. Mirrors the floating-pill aesthetic of `<AppHeader>`: a glassy capsule with `start` (links) and `end` (social) slots, plus a small brand line below. Helpers `<FooterLinks>`, `<FooterLink>`, and `<FooterSocial>` keep spacing and ARIA consistent across mini-apps.
PhoneInput
International phone field with country picker. Default country is auto-detected from the user's IANA timezone (e.g. KSA for `Asia/Riyadh`). Numbers always render LTR even inside RTL regions.
InputOTP
Segmented one-time-code input. Used for the SMS code step of the Rahhal phone-OTP sign-in flow, but generic enough for any 4–8 digit verification code. Built on the `input-otp` library; logical Tailwind utilities mean the rounded ends and divider borders flip correctly under RTL.
LoginScreen (demo)
A UI-only walk-through of the canonical Rahhal phone-OTP login: phone step → OTP step → success. Two layouts share the same form body — `split` (default in this demo) adds a brand-led marketing pane on the start edge at `lg+` (hidden on mobile); `centered` is the focused single-card surface. The screens here are **not** wired to real auth — they navigate to a demo signed-in page. Production mini-apps consume the same UX via `<LoginScreen>` from `@rahhal/auth/react` and exchange the code with Supabase.
RequireRole
Render-gate that requires both an active session and a named role string in the user's `app_metadata.roles` claim. Compose at the top of a protected subtree (typically inside `<AuthProvider>` and above all admin routes) so the gate runs once. Roles are issued backend-side by writing `auth.users.app_metadata.roles`; this component is read-only.
AccessDeniedScreen
Full-screen "you don't have permission" surface. Pairs with `<RequireRole>` from `@rahhal/auth/react` as the default `deniedFallback` when a signed-in user is missing the role required to view a protected subtree. Same anatomy as `<EmptyState>` (icon · title · description · action) but with a destructive-tinted icon tile and a viewport-filling default layout.