Mobile-Responsive Layout
Loquent uses a three-breakpoint responsive system: 375px (mobile), 768px (tablet), and 1280px+ (desktop). The sidebar becomes a full-screen overlay drawer on mobile, controlled by a hamburger menu with auto-close behavior.
Sidebar Drawer
Section titled “Sidebar Drawer”The sidebar_collapsed signal (Signal<bool>) drives the sidebar across all screen sizes.
| State | Mobile (<768px) | Desktop (≥768px) |
|---|---|---|
true (collapsed) | Hidden (hidden md:flex) | Icon-only (w-20) |
false (expanded) | Fixed overlay (z-50, w-64) + backdrop (z-40) | Full width (w-64) |
Responsive classes on the sidebar
Section titled “Responsive classes on the sidebar”class: tw_merge!( "bg-card text-card-foreground flex flex-col transition-all duration-300", if is_collapsed() { "hidden md:flex w-20" } else { "fixed inset-y-0 left-0 z-50 w-64 md:relative md:inset-auto md:z-auto" }),On mobile, the collapsed state hides the sidebar entirely. When expanded, it renders as a fixed overlay at z-50.
MobileSidebarBackdrop
Section titled “MobileSidebarBackdrop”A shared component renders a semi-transparent overlay behind the sidebar on mobile:
#[component]fn MobileSidebarBackdrop(sidebar_collapsed: Signal<bool>) -> Element { rsx! { if !sidebar_collapsed() { div { class: "fixed inset-0 z-40 bg-black/50 md:hidden", onclick: move |_| sidebar_collapsed.set(true), } } }}Tapping the backdrop closes the sidebar. The md:hidden class ensures it never renders on desktop.
Hamburger Menu
Section titled “Hamburger Menu”The header renders a toggle button on mobile via an optional sidebar_collapsed prop:
#[props(default)] sidebar_collapsed: Option<Signal<bool>>,
// Renders only on mobilebutton { class: "md:hidden p-2 rounded-lg hover:bg-muted transition-colors", onclick: move |_| collapsed.set(!collapsed()), PanelLeft { size: 20 }}Auto-Close on Navigation
Section titled “Auto-Close on Navigation”NavItem accepts Signal<bool> for is_collapsed and closes the sidebar when the user navigates — but only when the sidebar is currently expanded:
onclick: move |_| { if !is_collapsed() { is_collapsed.set(true); }},The guard prevents desktop users with an expanded sidebar from losing their preferred layout on every click.
All auto-close triggers
Section titled “All auto-close triggers”- NavItem click — collapses when sidebar is open
- Backdrop tap —
MobileSidebarBackdropsetssidebar_collapsed(true) - Logout — sidebar collapses before dispatching
on_logout
Admin — Icon-Only Tabs
Section titled “Admin — Icon-Only Tabs”Admin and Settings tabs use a shared icon-only pattern on mobile. Text labels hide below sm (640px), leaving just the icon as the tap target:
TabsList { class: "w-full justify-start overflow-x-auto overflow-y-hidden scrollbar-none", TabsTrigger { value: "overview", Shield { size: 16, class: "text-current" } span { class: "hidden sm:inline", "Overview" } } TabsTrigger { value: "organizations", Building2 { size: 16, class: "text-current" } span { class: "hidden sm:inline", "Organizations" } } // remaining tabs follow same pattern}overflow-x-auto overflow-y-hidden scrollbar-none enables horizontal scroll when tabs overflow on narrow screens. The same pattern applies to Settings tabs in settings_form_component.rs.
To add a new icon-only tab, pair your icon component with a span { class: "hidden sm:inline", "Label" } inside TabsTrigger.
Admin — Dual-Render Organizations Table
Section titled “Admin — Dual-Render Organizations Table”The Organizations tab renders two layouts side-by-side — a mobile card view and a desktop grid table. Only one is visible at any breakpoint:
// Mobile: card view, hidden at md (768px) and abovediv { class: "grid grid-cols-1 gap-3 md:hidden", for item in items { Link { to: Route::AdminOrgDetailsView { id: item.id.to_string() }, class: "block rounded-xl border bg-card p-4 space-y-3 transition-colors hover:bg-muted/60", // Header: icon + org name + activity count // Badge row: health status, Twilio mode, webhook gaps // Metrics grid: 4 equal columns } }}
// Desktop: 9-column grid table, hidden below mddiv { class: "hidden md:block overflow-hidden rounded-xl border bg-card", div { class: "grid grid-cols-[minmax(240px,1.4fr)_repeat(7,minmax(90px,1fr))_minmax(170px,1fr)] gap-3 ...", // Organization | Members | Phones | Calls | Messages | Twilio | SMS events | Webhook gaps | Activity }}Each mobile card contains three sections:
- Header row — org icon, name (truncated via
truncate), and total activity count - Badge row —
flex-wrapbadges for health status, Twilio mode, SMS events, webhook gaps, and activity delta - Metrics grid — 4-column grid with Members, Phones, Calls, and Messages counts
Use this dual-render pattern when a desktop table has 6+ columns. Keep the two render paths fully separate — no shared rendering logic.
Admin — Responsive Chart Headers
Section titled “Admin — Responsive Chart Headers”The AI Costs chart header stacks vertically on mobile and aligns horizontally at sm:
div { class: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", div { /* Chart title */ } select { class: "h-8 w-full sm:w-auto rounded-md border bg-background px-2 text-sm", // Full-width dropdown on mobile, auto-width on desktop }}Apply w-full sm:w-auto to form controls paired with headings so they fill available space on mobile without overflowing.
Plan Views — Mobile Adaptations
Section titled “Plan Views — Mobile Adaptations”Filter bar stacking
Section titled “Filter bar stacking”Plan list filters stack vertically on mobile and sit inline on desktop:
div { class: "flex flex-col md:flex-row md:items-center gap-2 w-full md:w-auto", div { class: "w-full md:w-44", SearchableSelect { /* Template filter */ } } div { class: "w-full md:w-44", SearchableSelect { /* Contact filter */ } }}State pills use min-h-[36px] md:min-h-0 to meet mobile touch-target guidelines (36px minimum).
Description truncation
Section titled “Description truncation”Long plan descriptions truncate on mobile with a toggle:
div { class: if show_full_description() { "" } else { "max-h-20 overflow-hidden md:max-h-none md:overflow-visible" }, Markdown { content: data.plan.description.clone() }}if data.plan.description.len() > 150 { button { class: "text-xs text-primary hover:underline mt-1 md:hidden", onclick: move |_| show_full_description.toggle(), if show_full_description() { "Show less" } else { "Show more" } }}Header layout
Section titled “Header layout”The plan detail header stacks vertically on mobile. Action buttons use md:ml-auto to right-align only on desktop:
div { class: "flex flex-col gap-2", h2 { class: "text-lg font-semibold", {data.plan.title.clone()} } div { class: "flex flex-wrap items-center gap-2", // status badge, autopilot toggle div { class: "flex items-center gap-1.5 md:ml-auto", // Pause / Resume / Stop buttons } }}AI Toolbar Scroll
Section titled “AI Toolbar Scroll”The AI instruction toolbar switches from wrapping to horizontal scroll on mobile:
div { class: "flex overflow-x-auto md:flex-wrap items-center gap-1.5 pb-2 md:pb-0", Button { class: "rounded-full whitespace-nowrap shrink-0", /* ... */ }}Key Files
Section titled “Key Files”| File | What it does |
|---|---|
src/shared/layouts/app_layout.rs | Layout shells, MobileSidebarBackdrop |
src/shared/components/sidebar.rs | Responsive sidebar visibility |
src/shared/components/header.rs | Hamburger menu button |
src/shared/components/nav_item.rs | Auto-close on navigation |
src/mods/admin/views/admin_view.rs | Admin icon-only tabs |
src/mods/admin/components/admin_organizations_tab_component.rs | Dual-render Organizations table |
src/mods/admin/components/admin_ai_costs_tab_component.rs | Responsive chart headers |
src/mods/settings/components/settings_form_component.rs | Settings icon-only tabs |
src/mods/plan/components/plan_list_filter_bar_component.rs | Filter stacking |
src/mods/plan/views/plan_details_view.rs | Header layout, description truncation |
src/shared/ai_builder/components/ai_instruction_toolbar_component.rs | Horizontal scroll |