# Namespace UIKit **Category**: docs **URL**: https://namespace-uikit.vercel.app/docs **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/index.mdx > Accessible React components for Namespace products and modern web applications. Namespace UIKit is a typed React component library built on accessible React primitives and Tailwind CSS. It includes the foundational and advanced components documented on this site. ## Start here - [Install UIKit](./getting-started) - [Browse components](./components) - [Read the AI index](/llms.txt) # Accordion **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/accordion **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/accordion.mdx > A collapsible content panel for organizing information in a compact space ## Import ```tsx import { Accordion } from "@thenamespace/uikit"; ``` ### Usage ```tsx import { Accordion } from "@thenamespace/uikit"; import { RefreshIcon, PackageIcon, ArrowDown01Icon, CreditCardIcon, Globe02Icon, ReceiptDollarIcon, ShoppingBag01Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; const items = [ { content: "Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.", icon: , title: "How do I place an order?", }, { content: "Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.", icon: , title: "Can I modify or cancel my order?", }, { content: "We accept all major credit cards, including Visa, Mastercard, and American Express.", icon: , title: "What payment methods do you accept?", }, { content: "Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.", icon: , title: "How much does shipping cost?", }, { content: "Yes, we ship to most countries. Please check our shipping rates and policies for more information.", icon: , title: "Do you ship internationally?", }, { content: "If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.", icon: , title: "How do I request a refund?", }, ]; export function Basic() { return ( {items.map((item, index) => ( {item.icon ? ( {item.icon} ) : null} {item.title} {item.content} ))} ); } ``` ### Anatomy Import the Accordion component and access all parts using dot notation. ```tsx import { Accordion } from "@thenamespace/uikit"; export default () => ( ); ``` ### Surface ```tsx import { Accordion } from "@thenamespace/uikit"; import { RefreshIcon, PackageIcon, ArrowDown01Icon, CreditCardIcon, Globe02Icon, ReceiptDollarIcon, ShoppingBag01Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; const items = [ { content: "Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.", icon: , title: "How do I place an order?", }, { content: "Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.", icon: , title: "Can I modify or cancel my order?", }, { content: "We accept all major credit cards, including Visa, Mastercard, and American Express.", icon: , title: "What payment methods do you accept?", }, { content: "Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.", icon: , title: "How much does shipping cost?", }, { content: "Yes, we ship to most countries. Please check our shipping rates and policies for more information.", icon: , title: "Do you ship internationally?", }, { content: "If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.", icon: , title: "How do I request a refund?", }, ]; export function Surface() { return ( {items.map((item, index) => ( {item.icon ? ( {item.icon} ) : null} {item.title} {item.content} ))} ); } ``` ### Multiple Expanded ```tsx import {Accordion} from "@thenamespace/uikit"; export function Multiple() { return ( Getting Started Learn the basics of Namespace UIKit and how to integrate it into your React project. This section covers installation, setup, and your first component. Core Concepts Understand the fundamental concepts behind Namespace UIKit, including the compound component pattern, styling with Tailwind CSS, and accessibility features. Advanced Usage Explore advanced features like custom variants, theme customization, and integration with other libraries in your React ecosystem. Best Practices Follow our recommended best practices for building performant, accessible, and maintainable applications with Namespace UIKit components. ); } ``` ### Controlled ```tsx "use client"; import React from "react"; import { Accordion, Button, useDisclosureGroupNavigation, } from "@thenamespace/uikit"; import { ArrowDown01Icon, ArrowUp01Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; const items = [ { content: "Learn the basics of Namespace UIKit and how to integrate it into your React project. This section covers installation, setup, and your first component.", id: "getting-started", title: "Getting Started", }, { content: "Understand the fundamental concepts behind Namespace UIKit, including the compound component pattern, styling with Tailwind CSS, and accessibility features.", id: "core-concepts", title: "Core Concepts", }, { content: "Explore advanced features like custom variants, theme customization, and integration with other libraries in your React ecosystem.", id: "advanced-usage", title: "Advanced Usage", }, ]; export function Controlled() { const [expandedKeys, setExpandedKeys] = React.useState( new Set(["getting-started"]), ); const itemIds = items.map((item) => item.id); const { isNextDisabled, isPrevDisabled, onNext, onPrevious } = useDisclosureGroupNavigation({ expandedKeys, itemIds, onExpandedChange: setExpandedKeys, }); return (

Expanded: {[...expandedKeys].join(", ") || "none"}

{items.map((item) => ( {item.title} {item.content} ))}
); } ``` ### Custom Indicator ```tsx "use client"; import React from "react"; import type { Key } from "@thenamespace/uikit"; import { Accordion } from "@thenamespace/uikit"; import { Add01Icon, ChevronsDownIcon, CircleChevronDownIcon, HugeiconsIcon, Remove01Icon, } from "@thenamespace/uikit/icons"; export function CustomIndicator() { const [expandedKeys, setExpandedKeys] = React.useState>(new Set([""])); return ( Using Plus/Minus Icon {expandedKeys.has("1") ? ( ) : ( )} This accordion uses a plus icon that transforms when expanded. The icon automatically rotates 45 degrees to form an X. Using Caret Icon This item uses a caret icon for the indicator. The rotation animation is applied automatically. Using Arrow Icon This item uses an arrow icon. Any icon you pass will receive the rotation animation when the item expands. ); } ``` ### Disabled State ```tsx import {Accordion} from "@thenamespace/uikit"; export function Disabled() { return (

Entire accordion disabled

Disabled Item 1 This content cannot be accessed when the accordion is disabled. Disabled Item 2 This content cannot be accessed when the accordion is disabled.

Individual items disabled

Active Item This item is active and can be toggled normally. Disabled Item This content cannot be accessed when the item is disabled. Another Active Item This item is also active and can be toggled.
); } ``` ### FAQ Layout ```tsx import { Accordion } from "@thenamespace/uikit"; import { ArrowDown01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function FAQ() { const categories = [ { items: [ { content: "Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.", title: "How do I place an order?", }, { content: "Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.", title: "Can I modify or cancel my order?", }, ], title: "General", }, { items: [ { content: "You can purchase a license directly from our website. Select the license type that fits your needs and proceed to checkout.", title: "How do I purchase a license?", }, { content: "A standard license is for personal use or small projects, while a pro license includes commercial use rights and priority support.", title: "What is the difference between a standard and a pro license?", }, ], title: "Licensing", }, { items: [ { content: "You can reach our support team through the contact form on our website, or email us directly at support@example.com.", title: "How do I get support?", }, ], title: "Support", }, ]; return (

Frequently Asked Questions

Everything you need to know about licensing and usage.

{categories.map((category) => (

{category.title}

{category.items.map((item, index) => ( {item.title} {item.content} ))}
))}
); } ``` ### Custom Styles ```tsx import { Accordion, cn } from "@thenamespace/uikit"; import { ArrowDown01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; const items = [ { content: "Stay informed about your account activity with real-time notifications.", iconUrl: "/assets/docs/3dicons/bell-small.png", subtitle: "Receive account activity updates", title: "Set Up Notifications", }, { content: "Enhance your browsing experience by installing our official browser extension", iconUrl: "/assets/docs/3dicons/compass-small.png", subtitle: "Connect your browser to your account", title: "Set up Browser Extension", }, { content: "Begin your journey into the world of digital collectibles by creating your first NFT. ", iconUrl: "/assets/docs/3dicons/mint-collective-small.png", subtitle: "Create your first collectible", title: "Mint Collectible", }, ]; export function CustomStyles() { return ( {items.map((item) => ( {item.iconUrl ? ( {item.title} ) : null}
{item.title} {item.subtitle}
{item.content}
))}
); } ``` ### Without Separator ```tsx import { Accordion } from "@thenamespace/uikit"; import { ArrowDown01Icon, CreditCardIcon, ReceiptDollarIcon, ShoppingBag01Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; const items = [ { content: "Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.", icon: , title: "How do I place an order?", }, { content: "Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.", icon: , title: "Can I modify or cancel my order?", }, { content: "We accept all major credit cards, including Visa, Mastercard, and American Express.", icon: , title: "What payment methods do you accept?", }, ]; export function WithoutSeparator() { return ( {items.map((item, index) => ( {item.icon ? ( {item.icon} ) : null} {item.title} {item.content} ))} ); } ``` ### Custom Render Function ```tsx "use client"; import { Accordion } from "@thenamespace/uikit"; import { RefreshIcon, PackageIcon, ArrowDown01Icon, CreditCardIcon, Globe02Icon, ReceiptDollarIcon, ShoppingBag01Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; const items = [ { content: "Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.", icon: , title: "How do I place an order?", }, { content: "Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.", icon: , title: "Can I modify or cancel my order?", }, { content: "We accept all major credit cards, including Visa, Mastercard, and American Express.", icon: , title: "What payment methods do you accept?", }, { content: "Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.", icon: , title: "How much does shipping cost?", }, { content: "Yes, we ship to most countries. Please check our shipping rates and policies for more information.", icon: , title: "Do you ship internationally?", }, { content: "If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.", icon: , title: "How do I request a refund?", }, ]; export function CustomRenderFunction() { return (
} > {items.map((item, index) => (
} >
} > ))} Clear selection ); } function DefaultDemo() { const [selected, setSelected] = useState(new Set()); const count = selected === "all" ? files.length : selected.size; return (
{(item) => ( {item.label} )} setSelected(new Set())} count={count} />
); } export const DemoDefaultExample = () => ; ``` The `ActionBar` component is a floating pill-shaped toolbar that animates in/out based on the `isOpen` prop. It uses a **Prefix / Content / Suffix** structure. Combine with [ListView](/react/components/list-view) for bulk selection rows. ## With Data Grid {/* DEMO action-bar-with-data-grid */} ```tsx "use client"; import { useCallback, useMemo, useState } from "react"; import { ActionBar } from "@thenamespace/uikit"; import { Avatar } from "@thenamespace/uikit/avatar"; import { Button } from "@thenamespace/uikit/button"; import { Chip } from "@thenamespace/uikit/chip"; import { DataGrid, type DataGridColumn } from "@thenamespace/uikit/data-grid"; import { Separator } from "@thenamespace/uikit/separator"; import { Tooltip } from "@thenamespace/uikit/tooltip"; import type { Selection } from "react-aria-components"; import { Icon } from "@/demos/icon"; type Employee = { avatar: string; department: string; email: string; id: number; joinDate: string; name: string; status: "Active" | "Inactive" | "Pending"; }; const employees: Employee[] = [ { avatar: "/assets/generated/avatar-20.jpg", department: "Product", email: "elena.rodriguez@company.com", id: 1, joinDate: "2024-01-28", name: "Elena Rodriguez", status: "Active", }, { avatar: "/assets/generated/avatar-21.jpg", department: "Design", email: "marcus.chen@company.com", id: 2, joinDate: "2024-02-03", name: "Marcus Chen", status: "Pending", }, { avatar: "/assets/generated/avatar-22.jpg", department: "Product", email: "priya.patel@company.com", id: 3, joinDate: "2024-03-04", name: "Priya Patel", status: "Active", }, { avatar: "/assets/generated/avatar-23.jpg", department: "Support", email: "james.o.brien@company.com", id: 4, joinDate: "2024-04-14", name: "James O'Brien", status: "Active", }, { avatar: "/assets/generated/avatar-24.jpg", department: "Support", email: "yuki.tanaka@company.com", id: 5, joinDate: "2024-05-08", name: "Yuki Tanaka", status: "Inactive", }, { avatar: "/assets/generated/avatar-25.jpg", department: "Sales", email: "amara.okafor@company.com", id: 6, joinDate: "2024-06-27", name: "Amara Okafor", status: "Pending", }, { avatar: "/assets/generated/avatar-26.jpg", department: "Engineering", email: "luca.bianchi@company.com", id: 7, joinDate: "2024-07-25", name: "Luca Bianchi", status: "Active", }, { avatar: "/assets/generated/avatar-27.jpg", department: "Design", email: "sofia.andersson@company.com", id: 8, joinDate: "2024-08-08", name: "Sofia Andersson", status: "Active", }, ]; const statusColors = { Active: "success", Inactive: "danger", Pending: "warning", } as const; const formatDate = (value: string) => new Date(value).toLocaleDateString("en-US", { day: "numeric", month: "short", year: "numeric", }); const employeeColumns: DataGridColumn[] = [ { accessorKey: "name", allowsSorting: true, cell: (employee) => (
{employee.name .split(" ") .map((part) => part[0]) .join("")}
{employee.name} {employee.email}
), header: "Employee", id: "name", isRowHeader: true, minWidth: 240, }, { accessorKey: "department", allowsSorting: true, header: "Department", id: "department", }, { accessorKey: "status", allowsSorting: true, cell: (employee) => ( {employee.status} ), header: "Status", id: "status", }, { accessorKey: "joinDate", allowsSorting: true, cell: (employee) => ( {formatDate(employee.joinDate)} ), header: "Joined", id: "joinDate", }, ]; function WithDataGridDemo() { const [data, setData] = useState(employees); const [selected, setSelected] = useState(new Set()); const count = selected === "all" ? data.length : selected.size; const selectedKeys = useMemo( () => (selected === "all" ? new Set(data.map((item) => item.id)) : selected), [data, selected], ); const remove = useCallback(() => { setData((current) => current.filter((item) => !selectedKeys.has(item.id))); setSelected(new Set()); }, [selectedKeys]); const exportSelected = useCallback(() => { const csv = [ "Name,Email,Department,Status,Join Date", ...data .filter((item) => selectedKeys.has(item.id)) .map( (item) => `${item.name},${item.email},${item.department},${item.status},${item.joinDate}`, ), ].join("\n"); const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = "employees.csv"; anchor.click(); URL.revokeObjectURL(url); }, [data, selectedKeys]); return (
item.id} selectedKeys={selected} selectionMode="multiple" showSelectionCheckboxes onSelectionChange={setSelected} /> 0}> {count} Clear selection
); } export const DemoWithDataGridExample = () => ; ``` Combine with [DataGrid](/react/components/data-grid) for bulk selection workflows. The ActionBar appears when rows are selected and provides contextual actions like edit, export, archive, or delete. ## Anatomy ```tsx import { ActionBar } from "@thenamespace/uikit"; import { Button, Chip, Separator, Tooltip } from "@thenamespace/uikit"; {count} Clear selection ; ``` All three sections (`Prefix`, `Content`, `Suffix`) are optional. Use `Separator` from `@thenamespace/uikit` between sections as needed. ## Responsive Labels Use the `action-bar__label` CSS class on any text you want hidden on mobile. Below the `sm` breakpoint (640px), elements with this class become `sr-only` — buttons collapse to icon-only while remaining accessible. ```tsx ``` ## CSS Classes ### Base Classes - `.action-bar` — Outer positioning wrapper. Fixed to viewport bottom-center with `pointer-events: none`. - `.action-bar__wrapper` — The visible pill surface. Restores `pointer-events: auto`, applies shadow. ### Element Classes - `.action-bar__prefix` — Leading section (badges, counts). - `.action-bar__content` — Middle section for main actions. - `.action-bar__suffix` — Trailing section (dismiss button). - `.action-bar__label` — Text that collapses to `sr-only` below 640px. ## API Reference ### ActionBar The root component. Extends `ToolbarProps` from `@thenamespace/uikit`. | Prop | Type | Default | Description | | ------------- | ---------------------------- | -------------- | ------------------------------------------------------------------------------ | | `isOpen` | `boolean` | — | Controls visibility with animated enter/exit. Required. | | `aria-label` | `string` | `"Actions"` | Accessible label for the toolbar. | | `isAttached` | `boolean` | `true` | Whether the toolbar has a surface background with full rounding. | | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | The orientation of the toolbar. | | `className` | `string` | — | Additional CSS classes applied to the toolbar wrapper. | | `children` | `ReactNode` | — | Content — typically `Prefix`, `Content`, `Suffix`, and `Separator` components. | ### ActionBar.Prefix Leading section container. | Prop | Type | Default | Description | | ----------- | ----------- | ------- | --------------------------------------------------------- | | `children` | `ReactNode` | — | Content for the leading section (badges, counts, labels). | | `className` | `string` | — | Additional CSS classes. | ### ActionBar.Content Middle section container for main actions. | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------------------------- | | `children` | `ReactNode` | — | Action buttons, dropdowns, etc. | | `className` | `string` | — | Additional CSS classes. | ### ActionBar.Suffix Trailing section container. | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ---------------------------------- | | `children` | `ReactNode` | — | Dismiss button, secondary actions. | | `className` | `string` | — | Additional CSS classes. | # Agenda **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/agenda **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/agenda.mdx > A composable calendar component with day, week, and month views for displaying and managing events with drag interactions. ## Usage {/* DEMO agenda-default */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; // @demo-title Default import { useCallback, useEffect, useMemo, useState } from "react"; import { CalendarDateTime } from "@internationalized/date"; import { Agenda, type AgendaEventData, useAgenda } from "@thenamespace/uikit"; const at = (year: number, month: number, day: number, hour: number, minute = 0) => new CalendarDateTime(year, month, day, hour, minute); function initialEvents(): AgendaEventData[] { const now = new Date(); const year = now.getFullYear(), month = now.getMonth() + 1, day = now.getDate(); return [ { color: "#10b981", end: at(year, month, day + 2, 23, 59), id: "allday-1", isAllDay: true, start: at(year, month, day, 0), title: "Company Holiday", }, { color: "#3b82f6", end: at(year, month, day, 23, 59), id: "allday-2", isAllDay: true, start: at(year, month, day, 0), title: "Team Offsite", }, { end: at(year, month, day, 9, 30), id: "1", start: at(year, month, day, 9), title: "Team Standup", }, { color: "#d946ef", end: at(year, month, day, 13), id: "2", start: at(year, month, day, 12), title: "Lunch", }, { color: "#3b82f6", end: at(year, month, day, 15, 30), id: "3", start: at(year, month, day, 14), title: "Design Review", }, { color: "#10b981", end: at(year, month, day, 16, 30), id: "4", start: at(year, month, day, 16), title: "1:1 with Manager", }, { color: "#f59e0b", end: at(year, month, day, 10), id: "5", start: at(year, month, day, 9), title: "Product Sync", }, { color: "#8b5cf6", end: at(year, month, day, 10, 15), id: "6", start: at(year, month, day, 9, 15), title: "Eng Huddle", }, { color: "#ef4444", end: at(year, month, day, 15, 30), id: "7", start: at(year, month, day, 14, 30), title: "Client Call", }, { color: "#06b6d4", end: at(year, month, day, 14, 20), id: "8", start: at(year, month, day, 14), title: "Quick Check-in", }, { color: "#84cc16", end: at(year, month, day, 15), id: "9", start: at(year, month, day, 14, 40), title: "Wrap-up Notes", }, { color: "#f59e0b", end: at(year, month, day - 1, 11, 30), id: "10", start: at(year, month, day - 1, 10), title: "Sprint Planning", }, { color: "#8b5cf6", end: at(year, month, day + 3, 16), id: "11", start: at(year, month, day + 3, 15), title: "Retro", }, { color: "#ef4444", end: at(year, month, day + 9, 16, 30), id: "12", start: at(year, month, day + 9, 16), title: "1:1 with Manager", }, { color: "#10b981", end: at(year, month, day + 13, 23, 59), id: "13", isAllDay: true, start: at(year, month, day + 13, 0), title: "Holiday", }, { color: "#10b981", end: at(year, month, day + 2, 12), id: "14", start: at(year, month, day + 2, 11), title: "Code Review", }, { color: "#3b82f6", end: at(year, month, day + 8, 10, 30), id: "15", start: at(year, month, day + 8, 9), title: "Board Meeting", }, { color: "#3b82f6", end: at(year, month, day, 11, 15), id: "16", start: at(year, month, day, 10, 15), status: "unconfirmed", title: "Planning", }, { color: "#6b7280", end: at(year, month, day + 1, 10), id: "17", isReadOnly: true, start: at(year, month, day + 1, 9), title: "Company All-Hands", }, ]; } function useMediaQuery(query: string): boolean { const [matches, setMatches] = useState(false); useEffect(() => { const media = window.matchMedia(query); const update = (event: MediaQueryListEvent | MediaQueryList) => setMatches(event.matches); update(media); media.addEventListener("change", update); return () => media.removeEventListener("change", update); }, [query]); return matches; } let nextEventId = 100; const eventColors = ["#3b82f6", "#10b981", "#f59e0b", "#d946ef", "#8b5cf6", "#ef4444", "#06b6d4"]; function Demo() { const seed = useMemo(initialEvents, []); const [events, setEvents] = useState(seed); const isMobile = useMediaQuery("(max-width: 639px)"); const create = useCallback( ({ end, start }: { end: CalendarDateTime; start: CalendarDateTime }) => { const id = String(nextEventId++); const color = eventColors[nextEventId % eventColors.length]; setEvents((current) => [ ...current, { color, end, id, start, title: "New Event", }, ]); }, [], ); const move = useCallback( (id: string, start: CalendarDateTime, end: CalendarDateTime) => setEvents((current) => current.map((event) => (event.id === id ? { ...event, end, start } : event)), ), [], ); const remove = useCallback( (id: string) => setEvents((current) => current.filter((event) => event.id !== id)), [], ); const state = useAgenda({ defaultView: "week", events, onEventCreate: isMobile ? undefined : create, onEventDelete: remove, onEventMove: isMobile ? undefined : move, onEventResize: isMobile ? undefined : move, weekDays: isMobile ? 3 : 7, }); return (
{state.view !== "month" ? ( <> {state.allDayLayout.map((item) => ( ))} {state.visibleDays.map((date) => ( {state.getEventsForDay(date).map((event) => ( ))} ))} ) : ( {state.visibleWeeks.map((week) => { const layout = state.getMonthRowLayout(week); return ( {layout.items.map((item) => ( ))} {week.map((date, column) => ( {state.getPerCellEvents(date, week).map((event) => ( ))} ))} ); })} )}
); } export const DemoDefaultExample = () => ; ``` ## Anatomy Import the Agenda component and the `useAgenda` hook. Access all subcomponents using dot notation. ```tsx import {Agenda, useAgenda} from "@thenamespace/uikit"; const agenda = useAgenda({ events: [...], defaultView: "week", }); {/* Day / Week view */} all-day {agenda.allDayLayout.map((item) => ( ))} {agenda.visibleDays.map((day) => ( {agenda.getEventsForDay(day).map((event) => ( ))} ))} {/* Month view */} {agenda.visibleWeeks.map((week, i) => { const rowLayout = agenda.getMonthRowLayout(week); return ( {rowLayout.items.map((item) => ( ))} {week.map((day, colIdx) => ( {agenda.getPerCellEvents(day, week).map((event) => ( ))} ))} ); })} ``` ## Views The Agenda supports three views controlled via the `ViewSelector` or programmatically through `setView`: - **Day** — single day with full time grid - **Week** — 7-day (or custom count) view with shared time grid - **Month** — calendar grid with spanning multi-day events ## Events Events are defined as an array of `AgendaEvent` objects: ```tsx interface AgendaEvent { id: string; title: string; start: CalendarDateTime; end: CalendarDateTime; color?: string; isAllDay?: boolean; isReadOnly?: boolean; status?: "confirmed" | "unconfirmed"; } ``` ### Unconfirmed Events Set `status: "unconfirmed"` to render an event with a dashed border and transparent background, indicating a tentative or pending event. ### Read-Only Events Set `isReadOnly: true` to prevent an event from being moved or resized. The event can still be selected but drag interactions are disabled and the resize handle is hidden. ## Drag Interactions All drag interactions work out of the box when callbacks are provided: - **Drag to create** — click and drag on an empty time slot to create a new event - **Drag to move** — drag an event to a different time or day - **Drag to resize** — drag the bottom edge of an event to change its duration - **Cross-day move** — drag an event horizontally to move it to a different day On mobile, drag interactions (create, move, resize) are disabled by default. The consumer controls this by passing `undefined` for the drag callbacks when on a small screen. ### Event Callbacks ```tsx const agenda = useAgenda({ events, onEventCreate: (event) => { /* { start, end } */ }, onEventMove: (id, start, end) => { /* moved */ }, onEventResize: (id, start, end) => { /* resized */ }, onEventDelete: (id) => { /* deleted via Delete/Backspace key */ }, onEventSelect: (id) => { /* selected */ }, }); ``` ## All-Day Events All-day events appear in a collapsible section above the time grid. Multi-day all-day events span across day columns. The section includes an expand/collapse toggle. When collapsed, event counts are shown per day (e.g. "2 events"). Customize the collapsed label with the `collapsedLabel` prop on `Agenda.AllDaySection`. ## Month View Features ### Spanning Events Multi-day all-day events render as bars spanning across the month grid row. Use `getMonthRowLayout(week)` to compute layout positions and `getPerCellEvents(day, week)` for per-cell events. ### Event Overflow `Agenda.MonthCell` limits visible events via the `maxEvents` prop (default: 2). Overflow shows a "N more" link that navigates to the day view. Customize the label with the `moreLabel` prop. ### Date Navigation Clicking a date number in the month grid navigates to that date in day view. The first day of each month shows the month name (e.g. "May 1"). ## Weekend Highlighting Saturday and Sunday columns automatically receive a subtle gray background (`data-weekend` attribute) in all views. ## Current Time Indicator A live indicator shows the current time in the time grid: - Displays a time label badge (e.g. "10:30 AM") in the time column - In week view, a faded line spans all columns with an active highlight on today's column - Nearby hour labels auto-hide to avoid overlap - Updates every minute ## CSS Classes ### Base - `.agenda` — Root container. Sets CSS custom properties for sizing. ### CSS Variables - `--agenda-slot-height` — Height of each hour slot (default: `60px`). - `--agenda-time-column-width` — Width of the time labels column (default: `58px`). - `--agenda-current-time-color` — Color of the current time indicator (default: `var(--color-danger)`). - `--agenda-event-radius` — Border radius of event cards (default: `var(--radius-md)`). ### Header - `.agenda__header` — Flex container for heading, view selector, and navigation. - `.agenda__heading` — Month/year title text. - `.agenda__navigation` — Wrapper for nav buttons and today button. - `.agenda__nav-button` — Override hook for navigation arrow buttons (uses Namespace UIKit Button). - `.agenda__today-button` — Override hook for the Today button (uses Namespace UIKit Button). - `.agenda__view-selector` — Override hook for the view selector (uses Namespace UIKit Segment). ### Day/Week View - `.agenda__week-header` — Row of day headers above the time grid. - `.agenda__day-header` — Individual day header with name and date. - `.agenda__time-grid` — Scrollable time grid container. - `.agenda__time-labels` — Sticky column of hour labels. - `.agenda__time-label` — Individual hour label. - `.agenda__day-column` — Column for a single day's events. - `.agenda__time-slot` — Individual hour slot row. ### Events - `.agenda__event` — Positioned event card in the time grid. - `.agenda__event-title` — Event title text. - `.agenda__event-time` — Event time range text. - `.agenda__resize-handle` — Bottom resize handle with hover indicator. ### All-Day Section - `.agenda__all-day-section` — Grid container for all-day events. - `.agenda__all-day-toggle` — Expand/collapse chevron button. - `.agenda__all-day-label` — "all-day" label text. - `.agenda__all-day-event` — All-day event bar. - `.agenda__all-day-summary` — Collapsed event count per day. ### Month View - `.agenda__month-grid` — Month grid container. - `.agenda__month-weekday-header` — Sticky weekday names row. - `.agenda__month-row` — Week row in the month grid. - `.agenda__month-cell` — Individual day cell. - `.agenda__month-cell-date` — Date number button (navigates to day view). - `.agenda__month-cell-more` — "N more" overflow link. - `.agenda__month-event` — Per-cell event in month view. - `.agenda__month-spanning-event` — Multi-day event bar spanning across cells. ### Interactive States - `[data-dragging]` — Applied during drag interactions. - `[data-resizing]` — Applied during resize. - `[data-selected="true"]` — Applied to selected events. - `[data-status="unconfirmed"]` — Dashed border style for tentative events. - `[data-readonly]` — Applied to read-only events. - `[data-weekend]` — Applied to weekend columns and cells. - `[data-today]` — Applied to today's date elements. - `[data-drop-target]` — Applied to the target cell during drag. ### Previews - `.agenda__create-preview` — Dashed preview rectangle during drag-to-create. - `.agenda__drop-preview` — Outlined preview at the target position during drag-to-move. ## API Reference ### useAgenda The main hook for managing agenda state. Returns all data and methods needed by the component. | Option | Type | Default | Description | | ----------------- | ------------------------------- | -------- | ------------------------------------------------------------ | | `events` | `AgendaEvent[]` | — | Array of events to display. Required. | | `defaultView` | `"day" \| "week" \| "month"` | `"week"` | Initial view. | | `view` | `"day" \| "week" \| "month"` | — | Controlled view state. | | `onViewChange` | `(view: AgendaView) => void` | — | Called when the view changes. | | `defaultDate` | `CalendarDate` | today | Initial focused date. | | `date` | `CalendarDate` | — | Controlled date state. | | `onDateChange` | `(date: CalendarDate) => void` | — | Called when the date changes. | | `startHour` | `number` | `0` | First visible hour in the time grid. | | `endHour` | `number` | `24` | Last visible hour in the time grid. | | `slotDuration` | `number` | `60` | Duration of each time slot in minutes. | | `onEventCreate` | `(event: {start, end}) => void` | — | Called when dragging to create a new event. | | `onEventDelete` | `(id: string) => void` | — | Called when Delete/Backspace is pressed on a selected event. | | `onEventMove` | `(id, start, end) => void` | — | Called when an event is dragged to a new position. | | `onEventResize` | `(id, start, end) => void` | — | Called when an event is resized. | | `onEventSelect` | `(id: string \| null) => void` | — | Called when an event is selected or deselected. | | `selectedEventId` | `string \| null` | — | Controlled selected event state. | ### Agenda Root component. Wraps children in context and Motion providers. Also supports all HTML `div` props. ### Agenda.Header Container for heading, view selector, and navigation controls. Also supports all HTML `div` props. ### Agenda.Heading Displays the current month and year (e.g. "May 2026"). Also supports all HTML `h1` props. ### Agenda.ViewSelector Segmented control for switching between day, week, and month views. Built on the Namespace UIKit Segment component. | Prop | Type | Default | Description | | ------ | ---------------------- | ------- | ---------------------------- | | `size` | `"sm" \| "md" \| "lg"` | `"sm"` | Size of the segment control. | ### Agenda.NavButton Navigation button for previous/next. Built on the Namespace UIKit Button component. | Prop | Type | Default | Description | | ------ | ---------------------- | ------- | ------------------------ | | `slot` | `"previous" \| "next"` | — | Direction of navigation. | ### Agenda.TodayButton Button to navigate to today's date. Built on the Namespace UIKit Button component. ### Agenda.AllDaySection Collapsible section for all-day events with a CSS grid layout for spanning events. | Prop | Type | Default | Description | | ---------------- | --------------------------- | ------------ | ---------------------------------------- | | `collapsedLabel` | `(count: number) => string` | `"N events"` | Custom label for collapsed event counts. | ### Agenda.AllDayEvent An all-day event bar positioned in the grid. | Prop | Type | Default | Description | | ---------- | ------------- | ------- | ---------------------------------- | | `event` | `AgendaEvent` | — | The event data. Required. | | `colStart` | `number` | — | Grid column start index (0-based). | | `colSpan` | `number` | — | Number of columns to span. | | `row` | `number` | — | Row index for stacking. | ### Agenda.Event A timed event card positioned absolutely in a day column. Supports drag-to-move and drag-to-resize. | Prop | Type | Default | Description | | ------- | ------------- | ------- | ------------------------- | | `event` | `AgendaEvent` | — | The event data. Required. | ### Agenda.MonthCell A day cell in the month grid. Limits visible events and shows overflow. | Prop | Type | Default | Description | | ------------------ | --------------------------- | ---------- | ------------------------------------------------- | | `date` | `CalendarDate` | — | The date for this cell. Required. | | `maxEvents` | `number` | `2` | Maximum number of events to show before overflow. | | `moreLabel` | `(count: number) => string` | `"N more"` | Custom label for the overflow link. | | `spanningRowCount` | `number` | `0` | Number of spanning event rows above this cell. | ### Agenda.MonthSpanningEvent A multi-day event bar in the month grid, positioned absolutely across cells. | Prop | Type | Default | Description | | ---------- | ------------- | ------- | -------------------------------- | | `event` | `AgendaEvent` | — | The event data. Required. | | `colStart` | `number` | — | Column start index (0-based). | | `colSpan` | `number` | — | Number of columns to span. | | `row` | `number` | — | Row index for vertical stacking. | ### Agenda.MonthEvent A per-cell event in the month grid. Supports drag-to-move across cells. | Prop | Type | Default | Description | | ------- | ------------- | ------- | ------------------------- | | `event` | `AgendaEvent` | — | The event data. Required. |
# AlertDialog **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/alert-dialog **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/alert-dialog.mdx > Modal dialog for critical confirmations requiring user attention and explicit action ## Import ```tsx import { AlertDialog } from "@thenamespace/uikit"; ``` ### Usage ```tsx "use client"; import {AlertDialog, Button} from "@thenamespace/uikit"; export function Default() { return ( Delete project permanently?

This will permanently delete My Awesome Project and all of its data. This action cannot be undone.

); } ``` ### Anatomy Import the AlertDialog component and access all parts using dot notation. ```tsx import { AlertDialog, Button } from "@thenamespace/uikit"; export default () => ( {/* Optional: Close button */} {/* Optional: Status icon */} ); ``` ### Statuses ```tsx "use client"; import {AlertDialog, Button} from "@thenamespace/uikit"; export function Statuses() { const examples = [ { actions: { cancel: "Stay Signed In", confirm: "Sign Out", }, body: "You'll need to sign in again to access your account. Any unsaved changes will be lost.", classNames: "bg-accent-soft text-accent-soft-foreground", header: "Sign out of your account?", status: "accent", trigger: "Sign Out", }, { actions: { cancel: "Not Yet", confirm: "Mark Complete", }, body: "This will mark the task as complete and notify all team members. The task will be moved to your completed list.", classNames: "bg-success-soft text-success-soft-foreground", header: "Complete this task?", status: "success", trigger: "Complete Task", }, { actions: { cancel: "Keep Editing", confirm: "Discard", }, body: "You have unsaved changes that will be permanently lost. Are you sure you want to discard them?", classNames: "bg-warning-soft text-warning-soft-foreground", header: "Discard unsaved changes?", status: "warning", trigger: "Discard Changes", }, { actions: { cancel: "Cancel", confirm: "Delete Account", }, body: "This will permanently delete your account and remove all your data from our servers. This action is irreversible.", classNames: "bg-danger-soft text-danger-soft-foreground", header: "Delete your account?", status: "danger", trigger: "Delete Account", }, ] as const; return (
{examples.map(({actions, body, classNames, header, status, trigger}) => ( {header}

{body}

))}
); } ``` ### Placements ```tsx "use client"; import {AlertDialog, Button} from "@thenamespace/uikit"; export function Placements() { const placements = ["auto", "top", "center", "bottom"] as const; return (
{placements.map((placement) => ( {placement === "auto" ? "Auto Placement" : `${placement.charAt(0).toUpperCase() + placement.slice(1)} Position`}

{placement === "auto" ? "Automatically positions at the bottom on mobile and center on desktop for optimal user experience." : `This dialog is positioned at the ${placement} of the viewport. Critical confirmations are typically centered for maximum attention.`}

))}
); } ``` ### Backdrop Variants ```tsx "use client"; import {AlertDialog, Button} from "@thenamespace/uikit"; export function BackdropVariants() { const variants = ["opaque", "blur", "transparent"] as const; return (
{variants.map((variant) => ( Backdrop: {variant.charAt(0).toUpperCase() + variant.slice(1)}

{variant === "opaque" ? "An opaque dark backdrop that completely obscures the background, providing maximum focus on the dialog." : variant === "blur" ? "A blurred backdrop that softly obscures the background while maintaining visual context." : "A transparent backdrop that keeps the background fully visible, useful for less critical confirmations."}

))}
); } ``` ### Sizes ```tsx "use client"; import { AlertDialog, Button } from "@thenamespace/uikit"; import { Rocket01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function Sizes() { const sizes = ["xs", "sm", "md", "lg", "cover"] as const; return (
{sizes.map((size) => ( Size: {size.charAt(0).toUpperCase() + size.slice(1)}

{size === "cover" ? ( <> This alert dialog uses the cover size variant. It spans the full screen with margins: 16px on mobile and 40px on desktop. Maintains rounded corners and standard padding. Perfect for critical confirmations that need maximum width while preserving alert dialog aesthetics. ) : ( <> This alert dialog uses the {size} size variant. On mobile devices, all sizes adapt to near full-width for optimal viewing. On desktop, each size provides a different maximum width to suit various content needs. )}

))}
); } ``` ### Custom Icon ```tsx "use client"; import { AlertDialog, Button } from "@thenamespace/uikit"; import { SquareUnlock01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function CustomIcon() { return ( Reset your password?

We'll send a password reset link to your email address. You'll need to create a new password to regain access to your account.

); } ``` ### Custom Backdrop ```tsx "use client"; import { AlertDialog, Button } from "@thenamespace/uikit"; import { Alert01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function CustomBackdrop() { return ( Permanently delete your account?

This action cannot be undone. All your data, settings, and content will be permanently removed from our servers. The dramatic red backdrop emphasizes the severity and irreversibility of this decision.

); } ``` ### Dismiss Behavior ```tsx "use client"; import { AlertDialog, Button } from "@thenamespace/uikit"; import { InformationCircleIcon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; export function DismissBehavior() { return (

isDismissable

Controls whether the alert dialog can be dismissed by clicking the overlay backdrop. Alert dialogs typically require explicit action, so this defaults to false. Set to true for less critical confirmations.

isDismissable = false

Clicking the backdrop won't close this alert dialog

Try clicking outside this alert dialog on the overlay - it won't close. You must use the action buttons to dismiss it.

isKeyboardDismissDisabled

Controls whether the ESC key can dismiss the alert dialog. Alert dialogs typically require explicit action, so this defaults to{" "} true. When set to false, the ESC key will be enabled.

isKeyboardDismissDisabled = true

ESC key is disabled

Press ESC - nothing happens. You must use the action buttons to dismiss this alert dialog.

); } ``` ### Close Methods ```tsx "use client"; import {AlertDialog, Button} from "@thenamespace/uikit"; export function CloseMethods() { return (

Using slot="close"

The simplest way to close a dialog. Add slot="close" to any Button component within the dialog. When clicked, it will automatically close the dialog.

Using slot="close"

Click either button below - both have slot="close" and will close the dialog automatically.

Using Dialog render props

Access the close method from the Dialog's render props. This gives you full control over when and how to close the dialog, allowing you to add custom logic before closing.

{(renderProps) => ( <> Using Dialog render props

The buttons below use the close method from render props. You can add validation or other logic before calling{" "} renderProps.close().

)}
); } ``` ### Controlled State ```tsx "use client"; import {AlertDialog, Button, useOverlayState} from "@thenamespace/uikit"; import React from "react"; export function Controlled() { const [isOpen, setIsOpen] = React.useState(false); const state = useOverlayState(); return (

With React.useState()

Control the alert dialog using React's useState{" "} hook for simple state management. Perfect for basic use cases.

Status:{" "} {isOpen ? "open" : "closed"}

Controlled with useState()

This alert dialog is controlled by React's useState hook. Pass{" "} isOpen and onOpenChange props to manage the dialog state externally.

With useOverlayState()

Use the useOverlayState hook for a cleaner API with convenient methods like open(), close(), and{" "} toggle().

Status:{" "} {state.isOpen ? "open" : "closed"}

Controlled with useOverlayState()

The useOverlayState hook provides dedicated methods for common operations. No need to manually create callbacks—just use{" "} state.open(), state.close(), or{" "} state.toggle().

); } ``` ### Custom Trigger ```tsx "use client"; import { AlertDialog, Button } from "@thenamespace/uikit"; import { Delete02Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function CustomTrigger() { return (

Delete Item

Permanently remove this item

Delete this item?

Use AlertDialog.Trigger to create custom trigger elements beyond standard buttons. This example shows a card-style trigger with icons and descriptive text.

); } ``` ### Custom Animations ```tsx "use client"; import { AlertDialog, Button } from "@thenamespace/uikit"; import { HugeiconsIcon, type IconSvgElement, SparklesIcon, Upload01Icon, } from "@thenamespace/uikit/icons"; const iconMap: Record = { "hugeicons:arrow-up-from-line": Upload01Icon, "hugeicons:sparkles": SparklesIcon, }; export function CustomAnimations() { const animations = [ { classNames: { backdrop: [ "data-[entering]:duration-400", "data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]", "data-[exiting]:duration-200", "data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]", ].join(" "), container: [ "data-[entering]:animate-in", "data-[entering]:fade-in-0", "data-[entering]:zoom-in-95", "data-[entering]:duration-400", "data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]", "data-[exiting]:animate-out", "data-[exiting]:fade-out-0", "data-[exiting]:zoom-out-95", "data-[exiting]:duration-200", "data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]", ].join(" "), }, description: "Physics-based elastic scaling. Simulates a high-damping spring system with fast transient response and prolonged settling time. Ideal for Alert Dialogs and Modals.", icon: "hugeicons:sparkles", name: "Kinematic Scale", }, { classNames: { backdrop: [ "data-[entering]:duration-500", "data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]", "data-[exiting]:duration-200", "data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]", ].join(" "), container: [ "data-[entering]:animate-in", "data-[entering]:fade-in-0", "data-[entering]:slide-in-from-bottom-4", "data-[entering]:duration-500", "data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]", "data-[exiting]:animate-out", "data-[exiting]:fade-out-0", "data-[exiting]:slide-out-to-bottom-2", "data-[exiting]:duration-200", "data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]", ].join(" "), }, description: "Simulates movement through a medium with fluid resistance. Eliminates mechanical linearity for a natural, grounded feel. Perfect for Bottom Sheets or Toasts.", icon: "hugeicons:arrow-up-from-line", name: "Fluid Slide", }, ]; return (
{animations.map(({ classNames, description, icon, name }) => { const iconData = iconMap[icon]; return ( {iconData ? ( ) : null} {name} Animation

{description}

); })}
); } ``` ### Custom Portal ```tsx "use client"; import {AlertDialog, Button} from "@thenamespace/uikit"; import {useCallback, useRef, useState} from "react"; export function CustomPortal() { const portalRef = useRef(null); const [portalContainer, setPortalContainer] = useState(null); const setPortalRef = useCallback((node: HTMLDivElement | null) => { portalRef.current = node; setPortalContainer(node); }, []); return (

Render alert dialogs inside a custom container instead of document.body

Apply transform: translateZ(0) to the container to create a new stacking context.

{!!portalContainer && ( Custom Portal

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

)}
); } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { AlertDialog, Button } from "@thenamespace/uikit"; function CustomAlertDialog() { return ( Custom Styled Alert

This alert dialog has custom styling applied via Tailwind classes

); } ``` ### Customizing the component classes To customize the AlertDialog component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .alert-dialog__backdrop { @apply bg-gradient-to-br from-black/60 to-black/80; } .alert-dialog__dialog { @apply rounded-2xl border border-red-500/20 shadow-2xl; } .alert-dialog__header { @apply gap-4; } .alert-dialog__icon { @apply size-16; } .alert-dialog__close-trigger { @apply rounded-full bg-white/10 hover:bg-white/20; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The AlertDialog component uses these CSS classes: #### Base Classes - `.alert-dialog__trigger` - Trigger element that opens the alert dialog - `.alert-dialog__backdrop` - Overlay backdrop behind the dialog - `.alert-dialog__container` - Positioning wrapper with placement support - `.alert-dialog__dialog` - Dialog content container - `.alert-dialog__header` - Header section for icon and title - `.alert-dialog__heading` - Heading text styles - `.alert-dialog__body` - Main content area - `.alert-dialog__footer` - Footer section for actions - `.alert-dialog__icon` - Icon container with status colors - `.alert-dialog__close-trigger` - Close button element #### Backdrop Variants - `.alert-dialog__backdrop--opaque` - Opaque colored backdrop (default) - `.alert-dialog__backdrop--blur` - Blurred backdrop with glass effect - `.alert-dialog__backdrop--transparent` - Transparent backdrop (no overlay) #### Status Variants (Icon) - `.alert-dialog__icon--default` - Default gray status - `.alert-dialog__icon--accent` - Accent blue status - `.alert-dialog__icon--success` - Success green status - `.alert-dialog__icon--warning` - Warning orange status - `.alert-dialog__icon--danger` - Danger red status ### Interactive States The component supports these interactive states: - **Focus**: `:focus-visible` or `[data-focus-visible="true"]` - Applied to trigger, dialog, and close button - **Hover**: `:hover` or `[data-hovered="true"]` - Applied to close button on hover - **Active**: `:active` or `[data-pressed="true"]` - Applied to close button when pressed - **Entering**: `[data-entering]` - Applied during dialog opening animation - **Exiting**: `[data-exiting]` - Applied during dialog closing animation - **Placement**: `[data-placement="*"]` - Applied based on dialog position (auto, top, center, bottom) ## API Reference ### AlertDialog | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------------------------ | | `children` | `ReactNode` | - | Trigger and container elements | ### AlertDialog.Trigger | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ---------------------- | | `children` | `ReactNode` | - | Custom trigger content | | `className` | `string` | - | CSS classes | ### AlertDialog.Backdrop | Prop | Type | Default | Description | | --------------------------- | ------------------------------------- | ---------- | ------------------------- | | `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | Backdrop overlay style | | `isDismissable` | `boolean` | `false` | Close on backdrop click | | `isKeyboardDismissDisabled` | `boolean` | `true` | Disable ESC key to close | | `isOpen` | `boolean` | - | Controlled open state | | `onOpenChange` | `(isOpen: boolean) => void` | - | Open state change handler | | `className` | `string \| (values) => string` | - | Backdrop CSS classes | | `UNSTABLE_portalContainer` | `HTMLElement` | - | Custom portal container | ### AlertDialog.Container | Prop | Type | Default | Description | | ----------- | ----------------------------------------- | -------- | ------------------------- | | `placement` | `"auto" \| "center" \| "top" \| "bottom"` | `"auto"` | Dialog position on screen | | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "cover"` | `"md"` | Alert Dialog size variant | | `className` | `string \| (values) => string` | - | Container CSS classes | ### AlertDialog.Dialog | Prop | Type | Default | Description | | ------------------ | ------------------------------------- | --------------- | -------------------------- | | `children` | `ReactNode \| ({close}) => ReactNode` | - | Content or render function | | `className` | `string` | - | CSS classes | | `role` | `string` | `"alertdialog"` | ARIA role | | `aria-label` | `string` | - | Accessibility label | | `aria-labelledby` | `string` | - | ID of label element | | `aria-describedby` | `string` | - | ID of description element | ### AlertDialog.Header | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------------------------------------- | | `children` | `ReactNode` | - | Header content (typically Icon and Heading) | | `className` | `string` | - | CSS classes | ### AlertDialog.Heading | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------ | | `children` | `ReactNode` | - | Heading text | | `className` | `string` | - | CSS classes | ### AlertDialog.Body | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------ | | `children` | `ReactNode` | - | Body content | | `className` | `string` | - | CSS classes | ### AlertDialog.Footer | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ----------------------------------------- | | `children` | `ReactNode` | - | Footer content (typically action buttons) | | `className` | `string` | - | CSS classes | ### AlertDialog.Icon | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------- | ---------- | -------------------- | | `children` | `ReactNode` | - | Custom icon element | | `status` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"danger"` | Status color variant | | `className` | `string` | - | CSS classes | ### AlertDialog.CloseTrigger | Prop | Type | Default | Description | | ----------- | ------------------------------ | ------- | ------------------- | | `children` | `ReactNode` | - | Custom close button | | `className` | `string \| (values) => string` | - | CSS classes | ### useOverlayState Hook ```tsx import { useOverlayState } from "@thenamespace/uikit"; const state = useOverlayState({ defaultOpen: false, onOpenChange: (isOpen) => console.log(isOpen), }); state.isOpen; // Current state state.open(); // Open dialog state.close(); // Close dialog state.toggle(); // Toggle state state.setOpen(); // Set state directly ``` ## Accessibility Implements [WAI-ARIA AlertDialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/): - **Focus trap**: Focus locked within alert dialog - **Keyboard**: `ESC` closes (when enabled), `Tab` cycles elements - **Screen readers**: Proper ARIA attributes with `role="alertdialog"` - **Scroll lock**: Body scroll disabled when open - **Required action**: Defaults to requiring explicit user action (no backdrop/ESC dismiss)
# Alert **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/alert **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/alert.mdx > Display important messages and notifications to users with status indicators ## Import ```tsx import { Alert } from "@thenamespace/uikit"; ``` ### Usage ```tsx import {Alert, Button, CloseButton, Spinner} from "@thenamespace/uikit"; import React from "react"; export function Basic() { return (
{/* Default - General information */} New features available Check out our latest updates including dark mode support and improved accessibility features. {/* Accent - Important information with action */} Update available A new version of the application is available. Please refresh to get the latest features and bug fixes. {/* Danger - Error with detailed steps */} Unable to connect to server We're experiencing connection issues. Please try the following:
  • Check your internet connection
  • Refresh the page
  • Clear your browser cache
{/* Without description */} Profile updated successfully {/* Custom indicator - Loading state */} Processing your request Please wait while we sync your data. This may take a few moments. {/* Without close button */} Scheduled maintenance Our services will be unavailable on Sunday, March 15th from 2:00 AM to 6:00 AM UTC for scheduled maintenance.
); } ``` ### Anatomy Import the Alert component and access all parts using dot notation. ```tsx import { Alert } from "@thenamespace/uikit"; export default () => ( ); ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { Alert } from "@thenamespace/uikit"; function CustomAlert() { return ( Custom Alert This alert has custom styling applied ); } ``` ### Customizing the component classes To customize the Alert component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .alert { @apply rounded-2xl shadow-lg; } .alert__title { @apply font-bold text-lg; } .alert--danger { @apply border-l-4 border-red-600; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The Alert component uses these CSS classes: #### Base Classes - `.alert` - Base alert container - `.alert__indicator` - Icon/indicator container - `.alert__content` - Content wrapper for title and description - `.alert__title` - Alert title text - `.alert__description` - Alert description text #### Status Variant Classes - `.alert--default` - Default gray status - `.alert--accent` - Accent blue status - `.alert--success` - Success green status - `.alert--warning` - Warning yellow/orange status - `.alert--danger` - Danger red status ### Interactive States The Alert component is primarily informational and doesn't have interactive states on the base component. However, it can contain interactive elements like buttons or close buttons. ## API Reference ### Alert Props | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------- | ----------- | ------------------------------ | | `status` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | The visual status of the alert | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | The alert content | ### Alert.Indicator Props | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ----------------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Custom indicator icon (defaults to status icon) | ### Alert.Content Props | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ----------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Content (typically Title and Description) | ### Alert.Title Props | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | The alert title text | ### Alert.Description Props | Prop | Type | Default | Description | | ----------- | ----------- | ------- | -------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | The alert description text |
# AppLayout **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/app-layout **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/app-layout.mdx > A scaffold layout that composes a full-height sidebar, a sticky navbar, a main content area, and an optional right-side aside panel. ## Usage {/* DEMO app-layout-default */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } > ); } export const DemoDefaultExample = () => ( ); ``` ## Anatomy `AppLayout` is a controlled scaffold that wires together the `Sidebar`, `Navbar`, an optional right-side `aside` panel, and the `main` region. Pass the sidebar, navbar, and aside as props and render the page content as children: ```tsx import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; export function DashboardLayout({ children }) { return ( } navbar={ {/* … */} } sidebar={ <> {/* … */} {/* … */} } > {children} ); } ``` Under the hood `AppLayout` renders a `Sidebar.Provider`, so you can use all of the [Sidebar](/react/components/sidebar) and [Navbar](/react/components/navbar) compound parts inside without additional setup. > **Do not wrap `AppLayout` with your own `Sidebar.Provider`.** It's already set up internally and would create a nested context that shadows the layout's sidebar state. Configure the sidebar through `AppLayout`'s `sidebarOpen`, `defaultSidebarOpen`, `onSidebarOpenChange`, `sidebarSide`, `sidebarVariant`, `sidebarCollapsible`, `navigate`, `reduceMotion`, and `toggleShortcut` props — they're all forwarded to the internal provider. ## Collapsible {/* DEMO app-layout-collapsible */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } > ); } export const DemoCollapsibleExample = () => ( ); ``` Set `sidebarCollapsible="icon"` to let the sidebar collapse into an icon-only rail. Press `Cmd+B` (or `Ctrl+B`) or click the sidebar trigger to toggle. ## Offcanvas {/* DEMO app-layout-offcanvas */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } > ); } export const DemoOffcanvasExample = () => ( ); ``` With `sidebarCollapsible="offcanvas"` the sidebar slides fully off-screen when collapsed, giving the main content area the full viewport width. ## Inset Sidebar `sidebarVariant="inset"` renders the sidebar inside a bordered card while still filling the full viewport height. The main content sits next to it flush with the viewport edge. ## Floating Sidebar {/* DEMO app-layout-floating-sidebar */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoFloatingSidebarExample = () => ( ); ``` `sidebarVariant="floating"` detaches the sidebar from the viewport edge with rounded corners and a shadow, a common pattern for product dashboards. ## Inset Dashboard {/* DEMO app-layout-inset-dashboard */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoInsetDashboardExample = () => ( ); ``` A full dashboard composition combining the `inset` sidebar variant, offcanvas collapse behavior, KPIs, and a rich user dropdown. ## Docs Site {/* DEMO app-layout-docs-site */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoDocsSiteExample = () => ( ); ``` A documentation-style layout with a search bar, theme segment, and a grouped navigation sidebar. ## With Breadcrumbs {/* DEMO app-layout-with-breadcrumbs */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoWithBreadcrumbsExample = () => ( ); ``` A project-management-style navbar with a multi-level breadcrumb trail reflecting the current navigation depth. ## With Aside {/* DEMO app-layout-with-aside */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoWithAsideExample = () => ( ); ``` Pass content to the `aside` prop to render a right-side panel — ideal for conversation details, metadata, or contextual tooling. The aside is hidden below the desktop breakpoint and slides off-screen when closed. Use `AppLayout.AsideTrigger` inside the navbar to let users toggle the aside open and closed. ## Content Scroll Mode {/* DEMO app-layout-content-scroll-mode */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoContentScrollModeExample = () => ( ); ``` By default, `AppLayout` uses page-level scrolling (`scrollMode="page"`). Set `scrollMode="content"` to keep the shell fixed to the viewport and move vertical scrolling into the main column instead. This is useful for app shells where the sidebar and navbar should stay pinned while only the content region scrolls. ## Complex {/* DEMO app-layout-complex */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoComplexExample = () => ( ); ``` A full-featured layout combining an agent-hub-style sidebar, compact spacing, multiple menu groups, chips, a user dropdown, and a navbar with breadcrumbs and actions. ## Resizable Sidebar and Aside Opt into user-resizable panels with `sidebarResizable` / `asideResizable`. Both flags render the shell inside a [`Resizable`](/react/components/resizable) group so users can drag the handles to adjust the layout. Sizes are persisted when `resizableAutoSaveId` is set. `sidebarResizable` requires `sidebarCollapsible="offcanvas"` or `"none"` — the icon-rail mode is not compatible with free-resize yet and falls back to the static layout. Resizable size props accept both percentages and CSS units. Numbers keep the existing percentage behavior, while strings let you set fixed constraints such as `"220px"` or `"18rem"`. Use `sidebarResizeBehavior="preserve-pixel-size"` or `asideResizeBehavior="preserve-pixel-size"` when that panel should keep its pixel width as the browser window changes size. ```tsx {children} ``` ## Persisted State When `AppLayout` is **uncontrolled** (no `sidebarOpen` / `asideOpen` props), it automatically writes `sidebar_state` and `aside_state` cookies on every toggle. To restore the state across page loads, read those cookies server-side and pass them as `defaultSidebarOpen` / `defaultAsideOpen`. Because the server renders with the correct state from the start, there is no flash and no hydration mismatch. ### Next.js App Router ```tsx // app/layout.tsx import { cookies } from "next/headers"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; export default async function Layout({ children, }: { children: React.ReactNode; }) { const store = await cookies(); const defaultSidebarOpen = store.get("sidebar_state")?.value !== "false"; const defaultAsideOpen = store.get("aside_state")?.value !== "false"; return ( } navbar={} > {children} ); } ``` ### Vite / CSR For client-only apps you can read the cookies with a library like `js-cookie`: ```tsx import Cookies from "js-cookie"; const defaultSidebarOpen = Cookies.get("sidebar_state") !== "false"; const defaultAsideOpen = Cookies.get("aside_state") !== "false"; ``` > Using `Sidebar.Provider` on its own without `AppLayout`? It writes the same `sidebar_state` cookie automatically. See [Sidebar > Persisted State](/react/components/sidebar#persisted-state). ## Aside Keyboard Shortcut Pass `asideToggleShortcut` to bind a keyboard combo to `toggleAside()`. The shortcut parser is shared with `toggleShortcut` — same `mod+.`, `shift+?`, `ctrl+alt+k` syntax. Disabled by default to avoid swallowing `Cmd+.` on macOS. ```tsx }> {children} ``` ## Mobile Aside Sheet By default the aside is hidden below `1024px`. Set `asideMobile="sheet"` to render it in a full-height `Sheet` instead, toggled by the same `AppLayout.AsideTrigger`. Use the `` slot to provide different mobile content (e.g. a condensed view): ```tsx }> {children} ``` If no `MobileAside` slot is provided, the `aside` prop is used inside the sheet. ## Toolbar and Footer Pass `toolbar` to render a second sticky row below the navbar, and `footer` for a pinned row at the bottom of the body column. Both are optional and tree-shake when unused. ## Mobile Behavior On viewports below `768px` the desktop sidebar is hidden. Use `AppLayout.MenuToggle` inside the `Navbar.Header` to open a `Sidebar.Mobile` sheet: ```tsx {/* … */} } sidebar={ <> {/* desktop sidebar */} {/* mobile sheet sidebar */} } > {children} ``` - `AppLayout.MenuToggle` is **mobile-only** via CSS — it's hidden on desktop and visible below the `md` breakpoint. - `Sidebar.Trigger` is hidden on mobile inside an `AppLayout` so the two controls never appear together. ## Client-Side Routing `AppLayout` forwards its `navigate` prop to both the internal `Sidebar.Provider` **and** any `Navbar` rendered inside it via the `navbar` prop. Pass your router's push function once and every `Sidebar.MenuItem`, `Navbar.Item`, and `Navbar.MenuItem` with an `href` will route through it: > **Already using a global `RouterProvider`?** If your app is wrapped in React Aria's [`RouterProvider`](https://react-spectrum.adobe.com/react-aria/routing.html), you can omit the `navigate` prop entirely — the layout's `Sidebar.MenuItem`, `Navbar.Item`, and `Navbar.MenuItem` all defer to it automatically. Pass `navigate` only when this layout needs a different router than the global one (an explicit `navigate` always wins). See the [Navbar](/react/components/navbar#option-1--global-routerprovider-recommended) and [Sidebar](/react/components/sidebar#option-1--global-routerprovider-recommended) routing guides. ### Next.js (App Router) ```tsx "use client"; import { useRouter } from "next/navigation"; import { AppLayout } from "@thenamespace/uikit"; export function DashboardLayout({ children }) { const router = useRouter(); return ( {children} ); } ``` > A `Navbar` rendered via the `navbar` prop inherits the layout's `navigate` automatically. If you set a `navigate` prop directly on the `Navbar`, that value wins — useful when the navbar needs a different router (e.g. an external shell app). See the [Navbar routing guide](/react/components/navbar#client-side-routing) for non-layout usage. ### React Router ```tsx import { useNavigate } from "react-router"; import { AppLayout } from "@thenamespace/uikit"; export function DashboardLayout({ children }) { const navigate = useNavigate(); return ( {children} ); } ``` ### TanStack Router ```tsx import { useRouter } from "@tanstack/react-router"; import { AppLayout } from "@thenamespace/uikit"; export function DashboardLayout({ children }) { const router = useRouter(); return ( router.navigate({ to: href })} navbar={/* … */} sidebar={/* … */} > {children} ); } ``` ## Controlled State Both the sidebar and aside panel can be controlled via props on `AppLayout`: ```tsx const [sidebarOpen, setSidebarOpen] = useState(true); const [asideOpen, setAsideOpen] = useState(true); } navbar={} sidebar={} > {children} ; ``` Inside children, you can reach for [`useSidebar`](/react/components/sidebar#usesidebar) to read or toggle the sidebar state, and [`useAppLayout`](#useapplayout) to read or toggle the aside state. ## Navbar Adaptation When a `Navbar` is rendered inside `AppLayout` (via the `navbar` prop), it automatically detects the parent layout and: - Drops its own sticky/floating/static positioning (the `AppLayout` header element owns positioning). - Clears border, margin, radius, and shadow overrides from `position="floating"`. - Sets `data-in-app-layout="true"` so you can style it further if needed. This means you can reuse the same `Navbar` composition standalone or inside an `AppLayout` without changing its markup. ### Resizable Aside {/* DEMO app-layout-resizable-aside */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoResizableAsideExample = () => ( ); ``` ### Resizable Sidebar {/* DEMO app-layout-resizable-sidebar */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } export const DemoResizableSidebarExample = () => ( } resizableAutoSaveId="app-layout-demo:resizable-sidebar" sidebar={} sidebarCollapsible="offcanvas" sidebarResizable sidebarDefaultSize="280px" sidebarMaxSize="420px" sidebarMinSize="220px" sidebarResizeBehavior="preserve-pixel-size" >

Resizable sidebar

Drag the vertical handle between the sidebar and the main area to resize. Reload the page — the layout is persisted via{" "} resizableAutoSaveId.

); ``` ### With Inset Sidebar {/* DEMO app-layout-with-inset-sidebar */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoWithInsetSidebarExample = () => ( ); ``` ### With Toolbar {/* DEMO app-layout-with-toolbar */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { AnalyticsUpIcon, DashboardSquare01Icon, HelpCircleIcon, Logout01Icon, Notification02Icon, Search01Icon, Settings01Icon, Task01Icon, UserIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { Avatar, Breadcrumbs, BreadcrumbsItem, Button, Chip, Dropdown, } from "@thenamespace/uikit"; import { AppLayout, Navbar, Sidebar } from "@thenamespace/uikit"; const navigation = [ { icon: DashboardSquare01Icon, label: "Dashboard" }, { icon: AnalyticsUpIcon, items: ["Overview", "Reports", "Conversions"], label: "Analytics", }, { badge: "New", icon: Task01Icon, label: "Tracker" }, { icon: Settings01Icon, items: ["General", "Team", "Notifications"], label: "Settings", }, ] as const; function StoryIcon({ icon }: { icon: typeof DashboardSquare01Icon }) { return ; } function Brand({ mobile = false }: { mobile?: boolean }) { return (
H
Namespace UIKit
); } function NavigationMenu({ expanded = true, floating = false, mobile = false, }: { expanded?: boolean; floating?: boolean; mobile?: boolean; }) { const items = floating ? navigation.map((item) => item.label === "Analytics" ? { ...item, items: ["Overview", "Reports"] as const } : item, ) : navigation; return ( {items.map((item) => ( {item.label} {item.items && !mobile ? ( ) : null} {item.badge && !mobile ? ( {item.badge} ) : null} {item.items && !mobile ? ( {item.items.map((child) => ( {child} ))} ) : null} ))} ); } function FooterMenu({ helpLabel = "Help & Information", mobile = false, showLogout = true, }: { helpLabel?: string; mobile?: boolean; showLogout?: boolean; }) { return ( {helpLabel} {showLogout ? ( Log out ) : null} ); } function AppSidebar({ expanded = true, floating = false, helpLabel, rail = true, }: { expanded?: boolean; floating?: boolean; helpLabel?: string; rail?: boolean; } = {}) { return ( <> {rail ? : null} ); } function AccountMenu({ image = false }: { image?: boolean }) { return ( Account Log out ); } function AppNavbar({ aside = false, avatarImage = false, simpleAccount = false, }: { aside?: boolean; avatarImage?: boolean; simpleAccount?: boolean; }) { return ( Dashboard {simpleAccount ? null : } {simpleAccount ? ( ) : ( )} {aside ? ( ) : null} ); } function MainContent({ description, long = false, title = "Dashboard", }: { description: string; long?: boolean; title?: string; }) { return (

{title}

{description}

{long ? Array.from({ length: 18 }, (_, index) => (

Scrollable application content row {index + 1}.

)) : null}
); } interface DemoProps { aside?: boolean; asideResizable?: boolean; avatarImage?: boolean; description: string; scrollMode?: "content" | "page"; sidebarCollapsible?: "icon" | "none" | "offcanvas"; sidebarResizable?: boolean; sidebarStory?: "compact" | "default"; sidebarVariant?: "floating" | "inset" | "sidebar"; title?: string; toolbar?: boolean; } function Demo({ aside = false, asideResizable = false, avatarImage = false, description, scrollMode = "page", sidebarCollapsible = "icon", sidebarResizable = false, sidebarStory = "default", sidebarVariant = "sidebar", title, toolbar = false, }: DemoProps) { return (

Details

Contextual project information and activity.

) : undefined } asideResizable={asideResizable} navbar={} resizableAutoSaveId={ sidebarResizable || asideResizable ? "app-layout-demo:resizable-sidebar" : undefined } scrollMode={scrollMode} sidebar={} sidebarCollapsible={sidebarCollapsible} sidebarDefaultSize={sidebarResizable ? "280px" : 18} sidebarMaxSize={sidebarResizable ? "420px" : 30} sidebarMinSize={sidebarResizable ? "220px" : 12} sidebarResizable={sidebarResizable} sidebarResizeBehavior={ sidebarResizable ? "preserve-pixel-size" : undefined } sidebarVariant={sidebarVariant} toolbar={ toolbar ? (
) : undefined } >
); } export const DemoWithToolbarExample = () => ( ); ``` ## CSS Classes ### Base Classes - `.app-layout__body` — The right column containing the navbar header and main content. Flex column, `min-w-0 flex-1`. - `.app-layout__header` — The sticky header row that wraps the `navbar` prop. `sticky top-0 z-40 shrink-0`. - `.app-layout__main` — Primary content area. `min-w-0 flex-1`. ### Aside Classes - `.app-layout__aside` — The right-side aside panel. Sticky, full viewport height, with a left border. Hidden below the `1024px` breakpoint. - `.app-layout__aside[data-state="closed"]` — Collapsed state. Width transitions to `0` and the inner content slides off via `translateX(100%)`. - `.app-layout__aside-trigger` — The aside toggle button. Hidden below `1024px` to match the aside itself. ### Toggle Classes - `.app-layout__menu-toggle` — The mobile-only sidebar menu toggle. Hidden on desktop, `display: inline-flex` below `768px`. ### CSS Variables - `--app-layout-aside-width` — Width of the aside panel when open (default: `320px`). - `--app-layout-aside-duration` — Aside open/close transition duration (default: `200ms`). The sidebar width (`--sidebar-width`), collapsed width (`--sidebar-width-collapsed`), and navbar height (`--navbar-height`) are inherited from the [Sidebar](/react/components/sidebar#css-variables) and [Navbar](/react/components/navbar#css-variables) components. ### Interactive States - **Mobile (`≤768px`)**: The desktop `Sidebar` and `Sidebar.Trigger` inside `[data-app-layout]` are hidden, and `AppLayout.MenuToggle` becomes visible. Use `Sidebar.Mobile` for the mobile sheet. - **Tablet/Mobile (`≤1024px`)**: The aside panel and aside trigger are hidden. - **Aside closed**: `[data-state="closed"]` on `.app-layout__aside` collapses width to `0` and slides the inner content off-screen. - **Reduced motion**: `@media (prefers-reduced-motion: reduce)` — disables aside width/transform transitions. ## API Reference ### AppLayout The root scaffold. Wraps a `Sidebar.Provider` and renders the sidebar, header (with the `navbar`), main content, and optional aside. | Prop | Type | Default | Description | | ----------------------- | --------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `sidebar` | `ReactNode` | — | Sidebar content rendered in the full-height side panel. Typically a `` element alongside a ``. | | `navbar` | `ReactNode` | — | Navbar content rendered inside the sticky header row of the body column. | | `aside` | `ReactNode` | — | Content rendered in the optional right-side aside panel (full viewport height, hidden on mobile). | | `children` | `ReactNode` | — | Main content area. Rendered inside a `
` element. | | `sidebarOpen` | `boolean` | — | Controlled sidebar open state. | | `defaultSidebarOpen` | `boolean` | `true` | Initial sidebar open state (uncontrolled). | | `onSidebarOpenChange` | `(open: boolean) => void` | — | Callback when the sidebar open state changes. | | `sidebarSide` | `"left" \| "right"` | `"left"` | Which side the sidebar is on. Forwarded to `Sidebar.Provider`. | | `sidebarVariant` | `"sidebar" \| "floating" \| "inset"` | `"sidebar"` | Sidebar visual variant. Forwarded to `Sidebar.Provider`. | | `sidebarCollapsible` | `"icon" \| "offcanvas" \| "none"` | `"icon"` | Sidebar collapse behavior. Forwarded to `Sidebar.Provider`. | | `asideOpen` | `boolean` | — | Controlled aside open state. | | `defaultAsideOpen` | `boolean` | `true` | Initial aside open state (uncontrolled). | | `onAsideOpenChange` | `(open: boolean) => void` | — | Callback when the aside open state changes. | | `navigate` | `(href: string) => void` | — | Programmatic navigation function for client-side routing. Forwarded to the internal `Sidebar.Provider` **and** inherited by any `Navbar` rendered inside the layout (unless the `Navbar` sets its own `navigate`). If omitted, links defer to a global React Aria `RouterProvider` when present. | | `reduceMotion` | `boolean` | `false` | Disables nested `Sidebar.Menu` expand/collapse animations. The user's reduced-motion preference is still respected. | | `toggleShortcut` | `string \| false \| null` | `"mod+b"` | Keyboard shortcut that toggles the sidebar. Forwarded to the internal `Sidebar.Provider`. See [Sidebar › Keyboard Shortcut](/react/components/sidebar#keyboard-shortcut) for the combo-string syntax. Pass `false` or `null` to disable. | | `asideToggleShortcut` | `string \| false \| null` | — | Keyboard shortcut that toggles the aside. Disabled by default. Same combo syntax as `toggleShortcut`. | | `asideMobile` | `"hidden" \| "sheet"` | `"hidden"` | How the aside behaves on viewports `≤1024px`. `"sheet"` renders the aside in a `Sheet` toggled by the same `AsideTrigger`. | | `sidebarResizable` | `boolean` | `false` | Make the sidebar user-resizable. Requires `sidebarCollapsible="offcanvas"` or `"none"`. | | `sidebarDefaultSize` | `number \| string` | `18` | Initial sidebar size when resizable. Numbers are percentages; strings accept CSS units. | | `sidebarMinSize` | `number \| string` | `12` | Minimum sidebar size when resizable. Numbers are percentages; strings accept CSS units. | | `sidebarMaxSize` | `number \| string` | `30` | Maximum sidebar size when resizable. Numbers are percentages; strings accept CSS units. | | `sidebarResizeBehavior` | `"preserve-relative-size" \| "preserve-pixel-size"` | `"preserve-relative-size"` | Whether the sidebar keeps its relative percentage or pixel size when the layout width changes. | | `asideResizable` | `boolean` | `false` | Make the aside user-resizable. | | `asideDefaultSize` | `number \| string` | `20` | Initial aside size when resizable. Numbers are percentages; strings accept CSS units. | | `asideMinSize` | `number \| string` | `15` | Minimum aside size when resizable. Numbers are percentages; strings accept CSS units. | | `asideMaxSize` | `number \| string` | `40` | Maximum aside size when resizable. Numbers are percentages; strings accept CSS units. | | `asideResizeBehavior` | `"preserve-relative-size" \| "preserve-pixel-size"` | `"preserve-relative-size"` | Whether the aside keeps its relative percentage or pixel size when the layout width changes. | | `resizableAutoSaveId` | `string` | — | `autoSaveId` forwarded to the internal `Resizable` group (enables persisted panel sizes). | | `scrollMode` | `"page" \| "content"` | `"page"` | Controls which element owns vertical scrolling. `"page"` keeps window/body scrolling; `"content"` makes the main column scroll while the shell stays fixed to the viewport. | | `toolbar` | `ReactNode` | — | Optional second sticky row rendered below the navbar. | | `footer` | `ReactNode` | — | Optional row pinned to the bottom of the body column. | Also supports all `
` props — they are forwarded to the underlying `Sidebar.Provider`. ### AppLayout.MenuToggle An icon button that opens the mobile sidebar sheet (`Sidebar.Mobile`). **Mobile-only** — hidden on viewports above `768px`. Place it inside your `Navbar.Header` alongside the `Sidebar.Trigger`. | Prop | Type | Default | Description | | -------------- | ----------------------- | ---------- | ------------------------------------------------------------------- | | `children` | `ReactNode` | `` | Custom icon. | | `tooltip` | `ReactNode` | — | Tooltip content. When omitted, no tooltip is rendered. | | `tooltipProps` | `AppLayoutTooltipProps` | — | Props forwarded to the internal `Tooltip` (delay, placement, etc.). | Also supports all [Namespace UIKit Button](https://namespace.com/docs/react/components/button) props. ### AppLayout.AsideTrigger An icon button that toggles the aside panel open and closed. Hidden below the desktop breakpoint. Place it inside the navbar (typically at the end of `Navbar.Content`). | Prop | Type | Default | Description | | --------------- | ----------------------- | ---------------------------- | ---------------------------------------------------------------------------------- | | `children` | `ReactNode` | `` | Custom icon. | | `closedTooltip` | `ReactNode` | — | Tooltip content when the aside is closed. | | `openTooltip` | `ReactNode` | — | Tooltip content when the aside is open. | | `tooltipProps` | `AppLayoutTooltipProps` | — | Props forwarded to the internal `Tooltip`. Applied to both open and closed states. | Also supports all [Namespace UIKit Button](https://namespace.com/docs/react/components/button) props. The rendered button exposes `aria-expanded` and `data-state="open" | "closed"` so you can style it contextually. ### AppLayout.MobileAside A marker component that provides alternative content for the mobile aside sheet (used when `asideMobile="sheet"`). Place it as a direct child of `AppLayout`. | Prop | Type | Description | | ---------- | ----------- | ------------------------------------------------ | | `children` | `ReactNode` | Mobile-only aside content. Replaces the `aside`. | ### AppLayoutTooltipProps Shared tooltip customization for `AppLayout.MenuToggle` and `AppLayout.AsideTrigger`. | Prop | Type | Default | Description | | ------------ | ---------------------------------------- | ---------- | ---------------------------------------------------- | | `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Tooltip placement relative to the trigger. | | `delay` | `number` | — | Delay in ms before showing the tooltip. | | `closeDelay` | `number` | — | Delay in ms before hiding the tooltip. | | `offset` | `number` | — | Offset from the trigger element in px. | | `showArrow` | `boolean` | `false` | Whether to show the tooltip arrow. | | `isDisabled` | `boolean` | `false` | Whether the tooltip is disabled. | | `className` | `string` | — | Class name applied to the `Tooltip.Content` element. | ### useAppLayout A hook for reading and mutating the aside state from inside `AppLayout`'s children. ```tsx const appLayout = useAppLayout(); if (appLayout) { const { isAsideOpen, setAsideOpen, toggleAside } = appLayout; } ``` | Property | Type | Description | | -------------- | ------------------------- | ------------------------------------------ | | `isAsideOpen` | `boolean` | Whether the aside panel is currently open. | | `setAsideOpen` | `(open: boolean) => void` | Set the aside open state. | | `toggleAside` | `() => void` | Toggle the aside open/closed. | The hook returns `null` when called outside of an `AppLayout`. For sidebar state, use [`useSidebar`](/react/components/sidebar#usesidebar) — the `AppLayout` sets up the same provider internally. # Area Chart **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/area-chart **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/area-chart.mdx > An area chart for visualizing trends with gradient fills, stacked series, and sparkline variants. ## Usage {/* DEMO area-chart-default */} ```tsx "use client"; import { AreaChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; import { ChartTooltip } from "@thenamespace/uikit/chart-tooltip"; const revenueData = [ { month: "Jan", revenue: 4200 }, { month: "Feb", revenue: 5800 }, { month: "Mar", revenue: 4900 }, { month: "Apr", revenue: 7200 }, { month: "May", revenue: 6100 }, { month: "Jun", revenue: 8400 }, { month: "Jul", revenue: 7800 }, { month: "Aug", revenue: 9200 }, { month: "Sep", revenue: 8600 }, { month: "Oct", revenue: 10200 }, { month: "Nov", revenue: 9800 }, { month: "Dec", revenue: 11500 }, ]; export const DemoDefaultExample = () => ( Monthly Revenue `${(value / 1000).toFixed(0)}k`} width={40} /> !active || !payload?.length ? null : ( {label} {payload.map((entry) => ( {entry.name} ${Number(entry.value).toLocaleString()} ))} ) } /> ); ``` ## Anatomy Import the AreaChart component and access all parts using dot notation. ```tsx import { AreaChart } from "@thenamespace/uikit"; ; ``` ## Custom Tooltip {/* DEMO area-chart-custom-tooltip */} ```tsx "use client"; import { AreaChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; import { ChartTooltip } from "@thenamespace/uikit/chart-tooltip"; const trafficData = [ { month: "Jan", organic: 2000, paidAds: 1000 }, { month: "Feb", organic: 5000, paidAds: 3000 }, { month: "Mar", organic: 8000, paidAds: 5000 }, { month: "Apr", organic: 7000, paidAds: 6000 }, { month: "May", organic: 9500, paidAds: 4000 }, { month: "Jun", organic: 8000, paidAds: 5500 }, { month: "Jul", organic: 12000, paidAds: 7000 }, { month: "Aug", organic: 11000, paidAds: 6500 }, { month: "Sep", organic: 14000, paidAds: 8000 }, { month: "Oct", organic: 13000, paidAds: 9000 }, { month: "Nov", organic: 16000, paidAds: 10000 }, { month: "Dec", organic: 15000, paidAds: 9500 }, ]; function AxisSet() { return ( <> value >= 1000 ? `${(value / 1000).toFixed(0)}k` : `${value}` } width={30} /> ); } function Legend({ items, }: { items: ReadonlyArray<{ color: string; label: string }>; }) { return (
{items.map(({ color, label }) => (
{label}
))}
); } export const DemoCustomTooltipExample = () => ( Sessions {[ ["custom-organic", "var(--chart-3)"], ["custom-paid", "var(--chart-1)"], ].map(([id, color]) => ( ))} { if ( !active || !payload?.length || !payload.every((entry) => typeof entry.value === "number") ) return null; const total = payload.reduce( (sum, entry) => sum + Number(entry.value ?? 0), 0, ); return ( {label} {payload.map((entry, index) => ( {entry.name} {Number(entry.value).toLocaleString()} ))}
Total {total.toLocaleString()}
); }} />
); ``` ## KPI With Area Chart {/* DEMO area-chart-kpiwith-area-chart */} ```tsx "use client"; import { AreaChart } from "@thenamespace/uikit"; import { KPI } from "@thenamespace/uikit/kpi"; import { TrendChip } from "@thenamespace/uikit/trend-chip"; const sparkUp = [30, 35, 28, 42, 38, 45, 50, 48, 55, 60, 58, 65].map((value) => ({ value })); const sparkDown = [65, 60, 62, 55, 58, 52, 50, 48, 45, 42, 44, 40].map((value) => ({ value })); const kpis = [ { color: "var(--chart-3)", data: sparkUp, direction: "up", id: "kpi-revenue", label: "Total Revenue", suffix: "last 30d", trend: "3.3%", value: "US$228,451", }, { color: "var(--color-danger)", data: sparkDown, direction: "down", id: "kpi-bounce", label: "Bounce Rate", suffix: "vs last 7d", trend: "5.9%", value: "42.3%", }, { color: "var(--color-success)", data: sparkUp, direction: "up", id: "kpi-users", label: "Active Users", suffix: "this month", trend: "10.9%", value: "98k", }, ] as const; export const DemoKPIWithAreaChartExample = () => (
{kpis.map((kpi) => ( {kpi.label}
{kpi.label === "Total Revenue" ? ( ) : kpi.label === "Bounce Rate" ? ( ) : ( )} {kpi.trend} {kpi.suffix}
))}
); ``` ## Multi Area {/* DEMO area-chart-multi-area */} ```tsx "use client"; import { AreaChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; const trafficData = [ { month: "Jan", organic: 2000, paidAds: 1000 }, { month: "Feb", organic: 5000, paidAds: 3000 }, { month: "Mar", organic: 8000, paidAds: 5000 }, { month: "Apr", organic: 7000, paidAds: 6000 }, { month: "May", organic: 9500, paidAds: 4000 }, { month: "Jun", organic: 8000, paidAds: 5500 }, { month: "Jul", organic: 12000, paidAds: 7000 }, { month: "Aug", organic: 11000, paidAds: 6500 }, { month: "Sep", organic: 14000, paidAds: 8000 }, { month: "Oct", organic: 13000, paidAds: 9000 }, { month: "Nov", organic: 16000, paidAds: 10000 }, { month: "Dec", organic: 15000, paidAds: 9500 }, ]; function AxisSet() { return ( <> value >= 1000 ? `${(value / 1000).toFixed(0)}k` : `${value}` } width={30} /> ); } function Legend({ items, }: { items: ReadonlyArray<{ color: string; label: string }>; }) { return (
{items.map(({ color, label }) => (
{label}
))}
); } export const DemoMultiAreaExample = () => ( Traffic Sources
231,856 Sessions
{[ ["organic-fill", "var(--chart-3)"], ["paidads-fill", "var(--chart-1)"], ].map(([id, color]) => ( ))} } />
); ``` ## Sparkline {/* DEMO area-chart-sparkline */} ```tsx "use client"; import { AreaChart } from "@thenamespace/uikit"; const sparkUp = [30, 35, 28, 42, 38, 45, 50, 48, 55, 60, 58, 65].map( (value) => ({ value }), ); const sparkDown = [65, 60, 62, 55, 58, 52, 50, 48, 45, 42, 44, 40].map( (value) => ({ value }), ); function SparkArea({ color, data, id, label, }: { color: string; data: { value: number }[]; id: string; label: string; }) { return (
{label}
); } export const DemoSparklineExample = () => (
); ``` ## Stacked {/* DEMO area-chart-stacked */} ```tsx "use client"; import { AreaChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; const trafficData = [ { month: "Jan", organic: 2000, paidAds: 1000 }, { month: "Feb", organic: 5000, paidAds: 3000 }, { month: "Mar", organic: 8000, paidAds: 5000 }, { month: "Apr", organic: 7000, paidAds: 6000 }, { month: "May", organic: 9500, paidAds: 4000 }, { month: "Jun", organic: 8000, paidAds: 5500 }, { month: "Jul", organic: 12000, paidAds: 7000 }, { month: "Aug", organic: 11000, paidAds: 6500 }, { month: "Sep", organic: 14000, paidAds: 8000 }, { month: "Oct", organic: 13000, paidAds: 9000 }, { month: "Nov", organic: 16000, paidAds: 10000 }, { month: "Dec", organic: 15000, paidAds: 9500 }, ]; function AxisSet() { return ( <> value >= 1000 ? `${(value / 1000).toFixed(0)}k` : `${value}` } width={30} /> ); } function Legend({ items, }: { items: ReadonlyArray<{ color: string; label: string }>; }) { return (
{items.map(({ color, label }) => (
{label}
))}
); } const stackedData = trafficData.map((item, index) => ({ ...item, direct: [ 800, 1500, 2200, 1800, 2600, 2000, 3100, 2800, 3500, 3200, 4000, 3700, ][index]!, referral: [ 500, 1200, 2100, 2800, 3200, 2600, 4100, 3800, 4500, 5200, 5800, 5100, ][index]!, })); export const DemoStackedExample = () => { const series = [ { color: "var(--chart-4)", key: "organic", label: "Organic" }, { color: "var(--chart-3)", key: "paidAds", label: "Paid Ads" }, { color: "var(--chart-2)", key: "referral", label: "Referral" }, { color: "var(--chart-1)", key: "direct", label: "Direct" }, ]; return ( Traffic Breakdown {series.map(({ color, key }) => ( ))} {series.map(({ color, key, label }) => ( ))} } /> ); }; ``` ## CSS Classes ### Element Classes - `.area-chart` — Root container wrapping `ResponsiveContainer` and the Recharts chart. ### Recharts Theming The following CSS rules target Recharts internal class names to apply Namespace UIKit design tokens automatically: - `.area-chart .recharts-cartesian-axis-tick-value` — Axis tick labels. 10px muted text. - `.area-chart .recharts-cartesian-axis-line` — Axis lines. Hidden by default. - `.area-chart .recharts-cartesian-axis-tick-line` — Tick lines. Hidden by default. - `.area-chart .recharts-cartesian-grid line` — Cartesian grid lines. Muted stroke at 0.15 opacity. - `.area-chart .recharts-tooltip-cursor` — Tooltip cursor. Dashed vertical line on hover. - `.area-chart .recharts-active-dot circle` — Active dot. Outlined with surface color for contrast. ## API Reference ### AreaChart The root wrapper. Renders a `ResponsiveContainer` + Recharts `AreaChart` with Namespace UIKit CSS theming applied automatically. | Prop | Type | Default | Description | | ---------- | ------------------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------- | | `data` | `Record[]` | — | Chart data — array of objects with numeric/string fields for each series. | | `height` | `number` | `300` | Chart height in pixels. | | `width` | ``number \| `${number}%` `` | `"100%"` | Chart width in pixels or percentage string. | | `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `{ top: 8, right: 8, bottom: 0, left: 0 }` | Recharts margin around the chart area. | | `children` | `ReactNode` | — | Recharts child components (`AreaChart.Area`, `AreaChart.XAxis`, etc.). | Also supports all native `div` HTML attributes. ### AreaChart.Area Re-exported Recharts `Area` component. Follows the [Recharts Area API](https://recharts.github.io/en-US/api/Area/). ### AreaChart.XAxis Re-exported Recharts `XAxis` component. Follows the [Recharts XAxis API](https://recharts.github.io/en-US/api/XAxis/). ### AreaChart.YAxis Re-exported Recharts `YAxis` component. Follows the [Recharts YAxis API](https://recharts.github.io/en-US/api/YAxis/). ### AreaChart.Grid Re-exported Recharts `CartesianGrid` component. Follows the [Recharts CartesianGrid API](https://recharts.github.io/en-US/api/CartesianGrid/). ### AreaChart.Tooltip Re-exported Recharts `Tooltip` component. Follows the [Recharts Tooltip API](https://recharts.github.io/en-US/api/Tooltip/). Use with `AreaChart.TooltipContent` for styled tooltips. ### AreaChart.TooltipContent Pre-built tooltip renderer for Recharts. Pass as the `content` prop of `AreaChart.Tooltip`. See [ChartTooltip](/react/components/chart-tooltip) for full props.
# Autocomplete **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/autocomplete **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/autocomplete.mdx > An autocomplete combines a select with filtering, allowing users to search and select from a list of options ## Import ```tsx import { Autocomplete, useFilter } from "@thenamespace/uikit"; ``` ### Usage ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, Tag, TagGroup, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export default function Default() { const {contains} = useFilter({sensitivity: "base"}); const [selectedKeys, setSelectedKeys] = useState([]); const items = [ {id: "florida", name: "Florida"}, {id: "delaware", name: "Delaware"}, {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "new-york", name: "New York"}, {id: "washington", name: "Washington"}, ]; const onRemoveTags = (keys: Set) => { setSelectedKeys((prev) => prev.filter((key) => !keys.has(key))); }; return ( setSelectedKeys(keys as Key[])} > {({defaultChildren, isPlaceholder, state}: any) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item: any) => item.key); return ( {selectedItemsKeys.map((selectedItemKey: Key) => { const item = items.find((s) => s.id === selectedItemKey); if (!item) return null; return ( {item.name} ); })} ); }} No results found}> {items.map((item) => ( {item.name} ))} ); } ``` ### Anatomy Import the Autocomplete component and access all parts using dot notation. ```tsx import { Autocomplete, Label, Description, SearchField, ListBox, } from "@thenamespace/uikit"; export default () => ( ); ``` ### With Description ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, Description, EmptyState, Label, ListBox, SearchField, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function WithDescription() { const [selectedKey, setSelectedKey] = useState(null); const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "florida", name: "Florida"}, {id: "delaware", name: "Delaware"}, {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "new-york", name: "New York"}, {id: "washington", name: "Washington"}, ]; return ( No results found}> {items.map((item) => ( {item.name} ))} Select your state of residence ); } ``` ### Multiple Select ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, Tag, TagGroup, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function MultipleSelect() { const [selectedKeys, setSelectedKeys] = useState([]); const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "florida", name: "Florida"}, {id: "new-york", name: "New York"}, {id: "illinois", name: "Illinois"}, {id: "pennsylvania", name: "Pennsylvania"}, ]; const onRemoveTags = (keys: Set) => { setSelectedKeys((prev) => prev.filter((key) => !keys.has(key))); }; return ( setSelectedKeys(keys as Key[])} > {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item) => item.key); return ( {selectedItemsKeys.map((selectedItemKey) => { const item = items.find((s) => s.id === selectedItemKey); if (!item) return null; return ( {item.name} ); })} ); }} No results found}> {items.map((item) => ( {item.name} ))} ); } ``` ### With Sections ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Header, Label, ListBox, SearchField, Separator, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function WithSections() { const [selectedKey, setSelectedKey] = useState(null); const {contains} = useFilter({sensitivity: "base"}); return ( No results found}>
North America
United States Canada Mexico
Europe
United Kingdom France Germany Spain Italy
Asia
Japan China India South Korea
); } ``` ### With Disabled Options ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@thenamespace/uikit"; import {useState} from "react"; export function WithDisabledOptions() { const [selectedKey, setSelectedKey] = useState(null); const {contains} = useFilter({sensitivity: "base"}); return ( No results found}> Dog Cat Bird Kangaroo Elephant Tiger ); } ``` ### Allows Empty Collection The `allowsEmptyCollection` prop enables the autocomplete to function even when there are no items in the collection. This is useful for scenarios where the list might be empty initially or when all items are filtered out. ```tsx "use client"; import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@thenamespace/uikit"; export function AllowsEmptyCollection() { const {contains} = useFilter({sensitivity: "base"}); return ( No results found} /> ); } ``` ### Custom Indicator ```tsx "use client"; import { useState } from "react"; import type { Key } from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter, } from "@thenamespace/uikit"; import { Icon } from "@/demos/icon"; export function CustomIndicator() { const [selectedKey, setSelectedKey] = useState(null); const { contains } = useFilter({ sensitivity: "base" }); const items = [ { id: "florida", name: "Florida" }, { id: "delaware", name: "Delaware" }, { id: "california", name: "California" }, { id: "texas", name: "Texas" }, { id: "new-york", name: "New York" }, { id: "washington", name: "Washington" }, ]; return ( No results found}> {items.map((item) => ( {item.name} ))} ); } ``` ### Required ```tsx "use client"; import { Autocomplete, Button, EmptyState, FieldError, Form, Label, ListBox, SearchField, useFilter, } from "@thenamespace/uikit"; export function Required() { const onSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const data: Record = {}; // Convert FormData to plain object formData.forEach((value, key) => { data[key] = value.toString(); }); alert("Form submitted successfully!"); }; const {contains} = useFilter({sensitivity: "base"}); const states = [ {id: "florida", name: "Florida"}, {id: "delaware", name: "Delaware"}, {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "new-york", name: "New York"}, {id: "washington", name: "Washington"}, ]; const countries = [ {id: "usa", name: "United States"}, {id: "canada", name: "Canada"}, {id: "mexico", name: "Mexico"}, {id: "uk", name: "United Kingdom"}, {id: "france", name: "France"}, {id: "germany", name: "Germany"}, ]; return (
No results found}> {states.map((state) => ( {state.name} ))} No results found}> {countries.map((country) => ( {country.name} ))}
); } ``` ### Full Width ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, Surface, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function FullWidth() { const [selectedKey, setSelectedKey] = useState(null); const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "florida", name: "Florida"}, {id: "delaware", name: "Delaware"}, {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "new-york", name: "New York"}, {id: "washington", name: "Washington"}, ]; return ( No results found}> {items.map((item) => ( {item.name} ))} ); } ``` ### Variants The Autocomplete component supports two visual variants: - **`primary`** (default) - Standard styling with shadow, suitable for most use cases - **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, Tag, TagGroup, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function Variants() { const [selectedKey1, setSelectedKey1] = useState(null); const [selectedKey2, setSelectedKey2] = useState(null); const [selectedKeys1, setSelectedKeys1] = useState([]); const [selectedKeys2, setSelectedKeys2] = useState([]); const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "option1", name: "Option 1"}, {id: "option2", name: "Option 2"}, {id: "option3", name: "Option 3"}, {id: "option4", name: "Option 4"}, ]; const onRemoveTags1 = (keys: Set) => { setSelectedKeys1((prev) => prev.filter((key) => !keys.has(key))); }; const onRemoveTags2 = (keys: Set) => { setSelectedKeys2((prev) => prev.filter((key) => !keys.has(key))); }; return (

Single Select Variants

No results found}> {items.map((item) => ( {item.name} ))} No results found}> {items.map((item) => ( {item.name} ))}

Multiple Select Variants

setSelectedKeys1(keys as Key[])} > {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item) => item.key); return ( {selectedItemsKeys.map((selectedItemKey) => { const item = items.find((s) => s.id === selectedItemKey); if (!item) return null; return ( {item.name} ); })} ); }} No results found}> {items.map((item) => ( {item.name} ))} setSelectedKeys2(keys as Key[])} > {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item) => item.key); return ( {selectedItemsKeys.map((selectedItemKey) => { const item = items.find((s) => s.id === selectedItemKey); if (!item) return null; return ( {item.name} ); })} ); }} No results found}> {items.map((item) => ( {item.name} ))}
); } ``` ### In Surface When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds. ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, Surface, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function FullWidth() { const [selectedKey, setSelectedKey] = useState(null); const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "florida", name: "Florida"}, {id: "delaware", name: "Delaware"}, {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "new-york", name: "New York"}, {id: "washington", name: "Washington"}, ]; return ( No results found}> {items.map((item) => ( {item.name} ))} ); } ``` ### Custom Value You can customize the displayed value using render props: ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, Avatar, AvatarFallback, AvatarImage, Description, EmptyState, Label, ListBox, SearchField, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function UserSelection() { const users = [ { avatarUrl: "/assets/avatars/blue.jpg", email: "bob@namespace.ninja", fallback: "B", id: "1", name: "Bob", }, { avatarUrl: "/assets/avatars/green.jpg", email: "fred@namespace.ninja", fallback: "F", id: "2", name: "Fred", }, { avatarUrl: "/assets/avatars/purple.jpg", email: "martha@namespace.ninja", fallback: "M", id: "3", name: "Martha", }, { avatarUrl: "/assets/avatars/red.jpg", email: "john@namespace.ninja", fallback: "J", id: "4", name: "John", }, { avatarUrl: "/assets/avatars/orange.jpg", email: "jane@namespace.ninja", fallback: "J", id: "5", name: "Jane", }, ]; const [selectedKey, setSelectedKey] = useState(null); const {contains} = useFilter({sensitivity: "base"}); return ( {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItems = state.selectedItems; if (selectedItems.length > 1) { return `${selectedItems.length} users selected`; } const selectedItem = users.find((user) => user.id === selectedItems[0]?.key); if (!selectedItem) { return defaultChildren; } return (
{selectedItem.fallback} {selectedItem.name}
); }}
No results found}> {users.map((user) => ( {user.fallback}
{user.email}
))}
); } ``` ### Controlled ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@thenamespace/uikit"; import {useState} from "react"; export function Controlled() { const states = [ {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "florida", name: "Florida"}, {id: "new-york", name: "New York"}, {id: "illinois", name: "Illinois"}, {id: "pennsylvania", name: "Pennsylvania"}, ]; const [state, setState] = useState("california"); const {contains} = useFilter({sensitivity: "base"}); const selectedState = states.find((s) => s.id === state); return (
No results found}> {states.map((state) => ( {state.name} ))}

Selected: {selectedState?.name || "None"}

); } ``` ### Controlled Multiple ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, Tag, TagGroup, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function MultipleSelect() { const [selectedKeys, setSelectedKeys] = useState([]); const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "florida", name: "Florida"}, {id: "new-york", name: "New York"}, {id: "illinois", name: "Illinois"}, {id: "pennsylvania", name: "Pennsylvania"}, ]; const onRemoveTags = (keys: Set) => { setSelectedKeys((prev) => prev.filter((key) => !keys.has(key))); }; return ( setSelectedKeys(keys as Key[])} > {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item) => item.key); return ( {selectedItemsKeys.map((selectedItemKey) => { const item = items.find((s) => s.id === selectedItemKey); if (!item) return null; return ( {item.name} ); })} ); }} No results found}> {items.map((item) => ( {item.name} ))} ); } ``` ### Controlled Open State ```tsx "use client"; import { Autocomplete, Button, EmptyState, Label, ListBox, SearchField, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function ControlledOpenState() { const [isOpen, setIsOpen] = useState(false); const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "florida", name: "Florida"}, {id: "delaware", name: "Delaware"}, {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "new-york", name: "New York"}, {id: "washington", name: "Washington"}, ]; return (
No results found}> {items.map((item) => ( {item.name} ))}

Autocomplete is {isOpen ? "open" : "closed"}

); } ``` ### Asynchronous Filtering ```tsx "use client"; import {Autocomplete, EmptyState, Label, ListBox, SearchField, Spinner} from "@thenamespace/uikit"; import {useAsyncList} from "@react-stately/data"; import {cn} from "tailwind-variants"; interface Character { name: string; } export function AsynchronousFiltering() { const list = useAsyncList({ async load({filterText, signal}) { const res = await fetch(`https://swapi.py4e.com/api/people/?search=${filterText}`, { signal, }); const json = await res.json(); return { items: json.results, }; }, }); return ( No results found} > {(item: Character) => ( {item.name} )} ); } ``` ### Virtualization Autocomplete supports virtualization through [Virtualizer](https://react-aria.adobe.com/Virtualizer), enabling efficient rendering of large datasets by displaying only the rows visible within the viewport. ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, Description, EmptyState, Label, ListBox, ListLayout, SearchField, Virtualizer, useFilter, } from "@thenamespace/uikit"; import {useMemo, useState} from "react"; interface User { email: string; id: number; name: string; } function generateUsers(n: number): User[] { const firstNames = [ "Emma", "Liam", "Olivia", "Noah", "Ava", "James", "Sophia", "Oliver", "Isabella", "Lucas", "Mia", "Ethan", "Charlotte", "Mason", "Amelia", "Logan", "Harper", "Alexander", "Ella", "Benjamin", ]; const lastNames = [ "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Anderson", "Taylor", "Thomas", "Jackson", "White", "Harris", "Clark", "Lewis", "Robinson", "Walker", ]; const users: User[] = []; for (let i = 0; i < n; i++) { const firstName = firstNames[i % firstNames.length]!; const lastName = lastNames[Math.floor(i / firstNames.length) % lastNames.length]!; const name = `${firstName} ${lastName}`; users.push({ email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@acme.com`, id: i + 1, name, }); } return users; } export function Virtualization() { const [selectedKey, setSelectedKey] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const {contains} = useFilter({sensitivity: "base"}); const allUsers = useMemo(() => generateUsers(1000), []); const filteredUsers = useMemo(() => { if (!searchQuery) return allUsers; return allUsers.filter( (user) => contains(user.name, searchQuery) || contains(user.email, searchQuery), ); }, [allUsers, contains, searchQuery]); return ( No results found} > {(user) => (
{user.email}
)}
); } ``` ### Disabled ```tsx "use client"; import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@thenamespace/uikit"; export function Disabled() { const {contains} = useFilter({sensitivity: "base"}); const items = [ {id: "florida", name: "Florida"}, {id: "delaware", name: "Delaware"}, {id: "california", name: "California"}, {id: "texas", name: "Texas"}, {id: "new-york", name: "New York"}, {id: "washington", name: "Washington"}, ]; const countries = [ {id: "argentina", name: "Argentina"}, {id: "venezuela", name: "Venezuela"}, {id: "japan", name: "Japan"}, {id: "france", name: "France"}, {id: "italy", name: "Italy"}, {id: "spain", name: "Spain"}, ]; return (
No results found}> {items.map((item) => ( {item.name} ))} No results found}> {countries.map((country) => ( {country.name} ))}
); } ``` ### Advanced Examples #### User Selection ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, Avatar, AvatarFallback, AvatarImage, Description, EmptyState, Label, ListBox, SearchField, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function UserSelection() { const users = [ { avatarUrl: "/assets/avatars/blue.jpg", email: "bob@namespace.ninja", fallback: "B", id: "1", name: "Bob", }, { avatarUrl: "/assets/avatars/green.jpg", email: "fred@namespace.ninja", fallback: "F", id: "2", name: "Fred", }, { avatarUrl: "/assets/avatars/purple.jpg", email: "martha@namespace.ninja", fallback: "M", id: "3", name: "Martha", }, { avatarUrl: "/assets/avatars/red.jpg", email: "john@namespace.ninja", fallback: "J", id: "4", name: "John", }, { avatarUrl: "/assets/avatars/orange.jpg", email: "jane@namespace.ninja", fallback: "J", id: "5", name: "Jane", }, ]; const [selectedKey, setSelectedKey] = useState(null); const {contains} = useFilter({sensitivity: "base"}); return ( {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItems = state.selectedItems; if (selectedItems.length > 1) { return `${selectedItems.length} users selected`; } const selectedItem = users.find((user) => user.id === selectedItems[0]?.key); if (!selectedItem) { return defaultChildren; } return (
{selectedItem.fallback} {selectedItem.name}
); }}
No results found}> {users.map((user) => ( {user.fallback}
{user.email}
))}
); } ``` #### User Selection Multiple ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, Avatar, AvatarFallback, AvatarImage, Description, EmptyState, Label, ListBox, SearchField, Tag, TagGroup, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function UserSelectionMultiple() { const users = [ { avatarUrl: "/assets/avatars/blue.jpg", email: "bob@namespace.ninja", fallback: "B", id: "1", name: "Bob", }, { avatarUrl: "/assets/avatars/green.jpg", email: "fred@namespace.ninja", fallback: "F", id: "2", name: "Fred", }, { avatarUrl: "/assets/avatars/purple.jpg", email: "martha@namespace.ninja", fallback: "M", id: "3", name: "Martha", }, { avatarUrl: "/assets/avatars/red.jpg", email: "john@namespace.ninja", fallback: "J", id: "4", name: "John", }, { avatarUrl: "/assets/avatars/orange.jpg", email: "jane@namespace.ninja", fallback: "J", id: "5", name: "Jane", }, ]; const [selectedKeys, setSelectedKeys] = useState([]); const {contains} = useFilter({sensitivity: "base"}); const onRemoveTags = (keys: Set) => { setSelectedKeys((prev) => prev.filter((key) => !keys.has(key))); }; return ( setSelectedKeys(keys as Key[])} > {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item) => item.key); return ( {selectedItemsKeys.map((selectedItemKey) => { const selectedItem = users.find((user) => user.id === selectedItemKey); if (!selectedItem) { return null; } return ( {selectedItem.fallback} {selectedItem.name} ); })} ); }} No results found}> {users.map((user) => ( {user.fallback}
{user.email}
))}
); } ``` #### Location Search ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, Description, EmptyState, Label, ListBox, SearchField, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; interface City { name: string; country: string; } export function LocationSearch() { const allCities: City[] = [ {country: "USA", name: "New York"}, {country: "USA", name: "Los Angeles"}, {country: "USA", name: "Chicago"}, {country: "UK", name: "London"}, {country: "France", name: "Paris"}, {country: "Japan", name: "Tokyo"}, {country: "Australia", name: "Sydney"}, {country: "Canada", name: "Toronto"}, {country: "Germany", name: "Berlin"}, {country: "Spain", name: "Madrid"}, ]; const [selectedKey, setSelectedKey] = useState(null); const [isLoading, setIsLoading] = useState(false); const {contains} = useFilter({sensitivity: "base"}); // Simulate async filtering const customFilter = (text: string, inputValue: string) => { if (!inputValue) return true; setIsLoading(true); setTimeout(() => setIsLoading(false), 300); return contains(text, inputValue); }; return ( ( {isLoading ? "Searching..." : "No cities found"} )} > {allCities.map((city) => (
{city.country}
))}
); } ``` #### Tag Group Selection ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, EmptyState, Label, ListBox, SearchField, Tag, TagGroup, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function TagGroupSelection() { const tags = [ {id: "react", name: "React"}, {id: "typescript", name: "TypeScript"}, {id: "javascript", name: "JavaScript"}, {id: "nodejs", name: "Node.js"}, {id: "python", name: "Python"}, {id: "vue", name: "Vue"}, {id: "angular", name: "Angular"}, {id: "nextjs", name: "Next.js"}, ]; const [selectedKeys, setSelectedKeys] = useState([]); const {contains} = useFilter({sensitivity: "base"}); const onRemoveTags = (keys: Set) => { setSelectedKeys((prev) => prev.filter((key) => !keys.has(key))); }; return ( setSelectedKeys(keys as Key[])} > {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item) => item.key); return ( {selectedItemsKeys.map((selectedItemKey) => { const tag = tags.find((t) => t.id === selectedItemKey); if (!tag) return null; return ( {tag.name} ); })} ); }} No tags found}> {tags.map((tag) => ( {tag.name} ))} ); } ``` #### Email Recipients ```tsx "use client"; import type {Key} from "@thenamespace/uikit"; import { Autocomplete, Description, EmptyState, Label, ListBox, SearchField, Tag, TagGroup, useFilter, } from "@thenamespace/uikit"; import {useState} from "react"; export function EmailRecipients() { const emails = [ {email: "alice@example.com", id: "alice@example.com", name: "Alice Johnson"}, {email: "bob@example.com", id: "bob@example.com", name: "Bob Smith"}, {email: "charlie@example.com", id: "charlie@example.com", name: "Charlie Brown"}, {email: "diana@example.com", id: "diana@example.com", name: "Diana Prince"}, {email: "eve@example.com", id: "eve@example.com", name: "Eve Wilson"}, ]; const [selectedKeys, setSelectedKeys] = useState([]); const {contains} = useFilter({sensitivity: "base"}); const onRemoveTags = (keys: Set) => { setSelectedKeys((prev) => prev.filter((key) => !keys.has(key))); }; return ( setSelectedKeys(keys as Key[])} > {({defaultChildren, isPlaceholder, state}) => { if (isPlaceholder || state.selectedItems.length === 0) { return defaultChildren; } const selectedItemsKeys = state.selectedItems.map((item) => item.key); return ( {selectedItemsKeys.map((selectedItemKey) => { const email = emails.find((e) => e.id === selectedItemKey); if (!email) return null; return ( {email.email} ); })} ); }} No recipients found}> {emails.map((email) => (
{email.email}
))}
); } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { Autocomplete, SearchField, ListBox } from "@thenamespace/uikit"; function CustomAutocomplete() { return ( Item 1 ); } ``` ### Customizing the component classes To customize the Autocomplete component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .autocomplete { @apply flex flex-col gap-1; } .autocomplete__trigger { @apply rounded-lg border border-border bg-surface p-2; } .autocomplete__value { @apply text-current; } .autocomplete__clear-button { @apply text-muted hover:text-foreground; } .autocomplete__indicator { @apply text-muted; } .autocomplete__popover { @apply rounded-lg border border-border bg-surface p-2; } .autocomplete__popover-dialog { @apply outline-none; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The Autocomplete component uses these CSS classes: #### Base Classes - `.autocomplete` - Base autocomplete container - `.autocomplete__trigger` - The button that triggers the autocomplete - `.autocomplete__value` - The displayed value or placeholder - `.autocomplete__clear-button` - The clear button that removes the selected value - `.autocomplete__indicator` - The dropdown indicator icon - `.autocomplete__popover` - The popover container - `.autocomplete__popover-dialog` - Internal dialog wrapper inside the popover for focus management (matches [Popover](/docs/components/popover) behavior) - `.autocomplete__filter` - The filter wrapper #### Variant Classes - `.autocomplete--primary` - Primary variant with shadow (default) - `.autocomplete--secondary` - Secondary variant without shadow, suitable for use in surfaces #### State Classes - `.autocomplete[data-invalid="true"]` - Invalid state - `.autocomplete__trigger[data-focus-visible="true"]` - Focused trigger state - `.autocomplete__trigger[data-disabled="true"]` - Disabled trigger state - `.autocomplete__value[data-placeholder="true"]` - Placeholder state - `.autocomplete__clear-button[data-empty="true"]` - Clear button hidden when no selection - `.autocomplete__indicator[data-open="true"]` - Open indicator state ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: - **Hover**: `:hover` or `[data-hovered="true"]` on trigger - **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on trigger - **Disabled**: `:disabled` or `[data-disabled="true"]` on autocomplete - **Open**: `[data-open="true"]` on indicator ## API Reference ### Autocomplete Props | Prop | Type | Default | Description | | ----------------------- | --------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `placeholder` | `string` | `'Select an item'` | Temporary text that occupies the autocomplete when it is empty | | `selectionMode` | `"single" \| "multiple"` | `"single"` | Whether single or multiple selection is enabled | | `allowsEmptyCollection` | `boolean` | `false` | Whether the autocomplete allows an empty collection. When true, the autocomplete can function even with no items. | | `isOpen` | `boolean` | - | Sets the open state of the popover (controlled) | | `defaultOpen` | `boolean` | - | Sets the default open state of the popover (uncontrolled) | | `onOpenChange` | `(isOpen: boolean) => void` | - | Handler called when the open state changes | | `disabledKeys` | `Iterable` | - | Keys of disabled items | | `isDisabled` | `boolean` | - | Whether the autocomplete is disabled | | `value` | `Key \| Key[] \| null` | - | Current value (controlled) | | `defaultValue` | `Key \| Key[] \| null` | - | Default value (uncontrolled) | | `onChange` | `(value: Key \| Key[] \| null) => void` | - | Handler called when the value changes | | `isRequired` | `boolean` | - | Whether user input is required | | `isInvalid` | `boolean` | - | Whether the autocomplete value is invalid | | `name` | `string` | - | The name of the input, used when submitting an HTML form | | `fullWidth` | `boolean` | `false` | Whether the autocomplete should take full width of its container | | `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Autocomplete content or render function | ### Autocomplete.Trigger Props | Prop | Type | Default | Description | | ----------- | ----------------------------- | ------- | ---------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Trigger content or render function | ### Autocomplete.Value Props | Prop | Type | Default | Description | | ----------- | ----------------------------- | ------- | -------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Value content or render function | ### Autocomplete.Indicator Props | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------------------ | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Custom indicator content | ### Autocomplete.ClearButton Props | Prop | Type | Default | Description | | ----------- | ------------------------------ | ------- | ------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `onClick` | `(e: MouseEvent) => void` | - | Handler called when button is clicked | | `ref` | `RefObject` | - | Ref to the clear button element | ### Autocomplete.Popover Props | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------- | | `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | Placement of the popover relative to the trigger | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Content children. Wrapped internally in a dialog element for focus management, styled with `.autocomplete__popover-dialog`. | ### Autocomplete.Filter Props | Prop | Type | Default | Description | | --------------- | ------------------------------------------ | ------- | ---------------------------------------- | | `filter` | `(text: string, input: string) => boolean` | - | Custom filter function | | `inputValue` | `string` | - | Controlled input value | | `onInputChange` | `(value: string) => void` | - | Handler called when input value changes | | `children` | `ReactNode` | - | Filter content (SearchField and ListBox) | ### useFilter Hook The `useFilter` hook from React Aria provides filtering functions for autocomplete functionality. ```tsx import { useFilter } from "@thenamespace/uikit"; const { contains } = useFilter({ sensitivity: "base" }); ... ... ; ``` **Options:** | Option | Type | Default | Description | | ------------- | ------------------------------------------- | -------- | ------------------------------- | | `sensitivity` | `"base" \| "accent" \| "case" \| "variant"` | `"base"` | Locale sensitivity for matching | **Returns:** | Function | Type | Description | | ------------ | ------------------------------------------------ | ------------------------------------------------------ | | `contains` | `(string: string, substring: string) => boolean` | Returns whether a string contains a given substring | | `startsWith` | `(string: string, substring: string) => boolean` | Returns whether a string starts with a given substring | | `endsWith` | `(string: string, substring: string) => boolean` | Returns whether a string ends with a given substring | ### RenderProps When using render functions with Autocomplete.Value, these values are provided: | Prop | Type | Description | | ----------------- | ------------- | ---------------------------------- | | `defaultChildren` | `ReactNode` | The default rendered value | | `isPlaceholder` | `boolean` | Whether the value is a placeholder | | `state` | `SelectState` | The state of the autocomplete | | `selectedItems` | `Node[]` | The currently selected items | ## Accessibility The Autocomplete component implements the ARIA select pattern with filtering and provides: - Full keyboard navigation support - Screen reader announcements for selection changes - Focus management aligned with [Popover](/docs/components/popover): `Autocomplete.Popover` wraps its content in an internal dialog so touch interactions do not show a stray focus ring on the popover overlay - Support for disabled states - Search functionality with filtering - HTML form integration Use `autoFocus={false}` on `SearchField` when you want to avoid opening the mobile keyboard as soon as the popover appears. Filtering still works once the user focuses the search input. For more information, see the [React Aria Select documentation](https://react-spectrum.adobe.com/react-aria/Select.html).
# Avatar **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/avatar **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/avatar.mdx > Display user profile images with customizable fallback content ## Import ```tsx import { Avatar } from "@thenamespace/uikit"; ``` ### Usage ```tsx import {Avatar} from "@thenamespace/uikit"; export function Basic() { return (
JD B JR
); } ``` ### Anatomy Import the Avatar component and access all parts using dot notation. ```tsx import { Avatar } from "@thenamespace/uikit"; export default () => ( ); ``` ### Sizes ```tsx import {Avatar} from "@thenamespace/uikit"; export function Sizes() { return (
SM MD LG
); } ``` ### Colors ```tsx import {Avatar} from "@thenamespace/uikit"; export function Colors() { return (
DF AC SC WR DG
); } ``` ### Variants ```tsx import { Avatar, Separator } from "@thenamespace/uikit"; import { UserIcon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function Variants() { const colors = ["accent", "default", "success", "warning", "danger"] as const; const variants = [ { content: "AG", label: "letter", type: "letter" }, { content: "AG", label: "letter soft", type: "letter-soft" }, { content: , label: "icon", type: "icon" }, { content: , label: "icon soft", type: "icon-soft", }, { content: [ "/assets/generated/avatar-3.jpg", "/assets/generated/avatar-4.jpg", "/assets/generated/avatar-5.jpg", "/assets/generated/avatar-8.jpg", "/assets/generated/avatar-16.jpg", ], label: "img", type: "img", }, ] as const; return (
{/* Color labels header */}
{colors.map((color) => (
{color}
))}
{/* Variant rows */} {variants.map((variant) => (
{variant.label}
{colors.map((color, colorIndex) => (
{variant.type === "img" ? ( <> {color.charAt(0).toUpperCase()} ) : ( {variant.content} )}
))}
))}
); } ``` ### Fallback Content ```tsx import { Avatar } from "@thenamespace/uikit"; import { UserIcon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function Fallback() { return (
{/* Text fallback */} JD {/* Icon fallback */} {/* Fallback with delay */} NA {/* Custom styled fallback */} GB
); } ``` ### Avatar Group ```tsx import {Avatar} from "@thenamespace/uikit"; const users = [ { id: 1, image: "/assets/avatars/blue.jpg", name: "John Doe", }, { id: 2, image: "/assets/avatars/green.jpg", name: "Kate Wilson", }, { id: 3, image: "/assets/avatars/purple.jpg", name: "Emily Chen", }, { id: 4, image: "/assets/avatars/orange.jpg", name: "Michael Brown", }, { id: 5, image: "/assets/avatars/red.jpg", name: "Olivia Davis", }, ]; export function Group() { return (
{/* Basic avatar group */}
{users.slice(0, 4).map((user) => ( {user.name .split(" ") .map((n) => n[0]) .join("")} ))}
{/* Avatar group with counter */}
{users.slice(0, 3).map((user) => ( {user.name .split(" ") .map((n) => n[0]) .join("")} ))} +{users.length - 3}
); } ``` ### Custom Styles ```tsx import {Avatar} from "@thenamespace/uikit"; export function CustomStyles() { return (
{/* Custom size with Tailwind classes */} XL {/* Square avatar */} SQ {/* Gradient border */}
GB
{/* Status indicator */}
ON
); } ``` ### Custom Image Component Use `asChild` on `Avatar.Image` to compose with a custom image component. This example uses Next.js `Image` for optimized loading. Pass `src` on `Avatar.Image` so it can track the loading state and show the fallback until the image is ready. ```tsx import Image from "next/image"; import { Avatar } from "@thenamespace/uikit"; const SRC = "/assets/avatars/blue.jpg"; export function CustomImageComponent() { return ( John Doe JD ); } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { Avatar } from "@thenamespace/uikit"; function CustomAvatar() { return ( XL ); } ``` ### Customizing the component classes To customize the Avatar component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .avatar { @apply size-16 border-2 border-primary; } .avatar__fallback { @apply bg-gradient-to-br from-purple-500 to-pink-500; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The Avatar component uses these CSS classes: #### Base Classes - `.avatar` - Base container with default size (size-10) - `.avatar__image` - Image element with aspect-square sizing - `.avatar__fallback` - Fallback container with centered content #### Size Modifiers - `.avatar--sm` - Small avatar (size-8) - `.avatar--md` - Medium avatar (default, no additional styles) - `.avatar--lg` - Large avatar (size-12) #### Variant Modifiers - `.avatar--soft` - Soft variant with lighter background #### Color Modifiers - `.avatar__fallback--default` - Default text color - `.avatar__fallback--accent` - Accent text color - `.avatar__fallback--success` - Success text color - `.avatar__fallback--warning` - Warning text color - `.avatar__fallback--danger` - Danger text color ## API Reference ### Avatar Props | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------- | ----------- | ---------------------- | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Avatar size | | `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | `'default'` | Fallback color theme | | `variant` | `'default' \| 'soft'` | `'default'` | Visual style variant | | `className` | `string` | - | Additional CSS classes | ### Avatar.Image Props | Prop | Type | Default | Description | | ------------- | --------------------------------------------------- | ------- | -------------------------------------------------- | | `src` | `string` | - | Image source URL | | `srcSet` | `string` | - | The image `srcset` attribute for responsive images | | `sizes` | `string` | - | The image `sizes` attribute for responsive images | | `alt` | `string` | - | Alternative text for the image | | `asChild` | `boolean` | `false` | Merge props onto the child element (e.g. `next/image`) instead of rendering a native `img` | | `onLoad` | `(event: SyntheticEvent) => void` | - | Callback when the image loads successfully | | `onError` | `(event: SyntheticEvent) => void` | - | Callback when there's an error loading the image | | `crossOrigin` | `'anonymous' \| 'use-credentials'` | - | CORS setting for the image request | | `loading` | `'eager' \| 'lazy'` | - | Native lazy loading attribute | | `className` | `string` | - | Additional CSS classes | ### Avatar.Fallback Props | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------- | ------- | ---------------------------------------------- | | `delayMs` | `number` | - | Delay before showing fallback (prevents flash) | | `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | - | Override color from parent | | `className` | `string` | - | Additional CSS classes | # Badge **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/badge **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/badge.mdx > Displays a small indicator positioned relative to another element, commonly used for notification counts, status dots, and labels ## Import ```tsx import { Badge } from "@thenamespace/uikit"; ``` ## Anatomy Badge is designed to be positioned relative to another element using `Badge.Anchor`. Plain-text children are automatically wrapped in ``. > For standalone label usage, use the [Chip](/docs/components/chip) component instead. ```tsx 5 ``` ### Usage ```tsx import {Avatar, Badge} from "@thenamespace/uikit"; const GREEN_AVATAR_URL = "/assets/avatars/green.jpg"; const ORANGE_AVATAR_URL = "/assets/avatars/orange.jpg"; const BLUE_AVATAR_URL = "/assets/avatars/blue.jpg"; export function BadgeBasic() { return (
JD 5 AB New CD
); } ``` ### Colors ```tsx import {Avatar, Badge} from "@thenamespace/uikit"; const AVATAR_URL = "/assets/avatars/green.jpg"; export function BadgeColors() { const colors = ["default", "accent", "success", "warning", "danger"] as const; return (
{colors.map((color) => ( JD ))}
); } ``` ### Sizes ```tsx import {Avatar, Badge} from "@thenamespace/uikit"; const AVATAR_URL = "/assets/avatars/green.jpg"; export function BadgeSizes() { const sizes = ["sm", "md", "lg"] as const; return (
{sizes.map((size) => ( JD 5 ))}
); } ``` ### Variants ```tsx import {Avatar, Badge, Separator} from "@thenamespace/uikit"; import React from "react"; const AVATAR_URL = "/assets/avatars/green.jpg"; export function BadgeVariants() { const variants = ["primary", "secondary", "soft"] as const; const colors = ["accent", "default", "success", "warning", "danger"] as const; return (
{variants.map((variant, index) => (

{variant}

{colors.map((color) => ( JD 5 ))}
{index < variants.length - 1 && }
))}
); } ``` ### Placements ```tsx import {Avatar, Badge} from "@thenamespace/uikit"; const AVATAR_URL = "/assets/avatars/green.jpg"; export function BadgePlacements() { const placements = ["top-right", "top-left", "bottom-right", "bottom-left"] as const; return (
{placements.map((placement) => (
JD {placement}
))}
); } ``` ### With Content Badge supports text, numbers, and icons as content. When no children are provided, it renders as a dot indicator. ```tsx import { Avatar, Badge } from "@thenamespace/uikit"; import { Notification01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; const AVATAR_URL = "/assets/avatars/green.jpg"; export function BadgeWithContent() { return (
JD 5 JD New JD 99+ JD
); } ``` ### Dot Badge Empty badges act as status indicators — useful for online/offline states or activity signals. ```tsx import {Avatar, Badge} from "@thenamespace/uikit"; const AVATAR_URL = "/assets/avatars/green.jpg"; export function BadgeDot() { const colors = ["accent", "success", "warning", "danger"] as const; return (
{colors.map((color) => ( JD ))}
); } ``` ## Styling ### Passing Tailwind CSS classes You can style the root container and individual slots: ```tsx import { Badge, Avatar } from "@thenamespace/uikit"; function CustomBadge() { return ( 99+ ); } ``` ### Customizing the component classes To customize the Badge component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .badge { @apply rounded-full text-xs; } .badge__label { @apply font-semibold; } .badge--accent { @apply shadow-sm; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The Badge component uses these CSS classes: #### Base Classes - `.badge` - Base badge container styles - `.badge__label` - Label text slot styles - `.badge-anchor` - Positioning wrapper for the anchored element #### Color Classes - `.badge--accent` - Accent color variant - `.badge--danger` - Danger color variant - `.badge--default` - Default color variant - `.badge--success` - Success color variant - `.badge--warning` - Warning color variant #### Variant Classes - `.badge--primary` - Primary variant with filled background - `.badge--secondary` - Secondary variant with default background - `.badge--soft` - Soft variant with lighter background #### Size Classes - `.badge--sm` - Small size - `.badge--md` - Medium size (default) - `.badge--lg` - Large size #### Placement Classes - `.badge--top-right` - Position at top-right corner (default) - `.badge--top-left` - Position at top-left corner - `.badge--bottom-right` - Position at bottom-right corner - `.badge--bottom-left` - Position at bottom-left corner #### Compound Variant Classes Badges support combining variant and color classes (e.g., `.badge--primary.badge--accent`). The following combinations have default styles defined: **Primary Variants:** - `.badge--primary.badge--accent` - Primary accent with filled background - `.badge--primary.badge--default` - Primary default with filled background - `.badge--primary.badge--success` - Primary success with filled background - `.badge--primary.badge--warning` - Primary warning with filled background - `.badge--primary.badge--danger` - Primary danger with filled background **Soft Variants:** - `.badge--soft.badge--accent` - Soft accent with lighter background - `.badge--soft.badge--default` - Soft default with lighter background - `.badge--soft.badge--success` - Soft success with lighter background - `.badge--soft.badge--warning` - Soft warning with lighter background - `.badge--soft.badge--danger` - Soft danger with lighter background ## API Reference ### Badge Props | Prop | Type | Default | Description | | ----------- | -------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------- | | `children` | `React.ReactNode` | - | Content to display inside the badge (text, number, or icon). When omitted, renders as a dot. | | `className` | `string` | - | Additional CSS classes for the root element | | `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Color variant of the badge | | `variant` | `"primary" \| "secondary" \| "soft"` | `"primary"` | Visual style variant | | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the badge | | `placement` | `"top-right" \| "top-left" \| "bottom-right" \| "bottom-left"` | `"top-right"` | Position of the badge relative to its anchor | ### Badge.Anchor Props | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | --------------------------------------------------------- | | `children` | `React.ReactNode` | - | The element to anchor the badge to, plus the Badge itself | | `className` | `string` | - | Additional CSS classes for the anchor wrapper | ### Badge.Label Props | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ----------------------------------------- | | `children` | `React.ReactNode` | - | Label text content | | `className` | `string` | - | Additional CSS classes for the label slot |
# Bar Chart **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/bar-chart **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/bar-chart.mdx > A bar chart for comparing categorical data with grouped, stacked, and horizontal layout support. ## Usage {/* DEMO bar-chart-default */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; import { ChartTooltip } from "@thenamespace/uikit/chart-tooltip"; import { Chip } from "@thenamespace/uikit/chip"; import { ArrowUp02Icon, Icon } from "@thenamespace/uikit/icons"; const sales = [18, 32, 28, 45, 38, 52, 42, 55, 48, 60, 53, 58].map((value, index) => ({ month: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][index] ?? "Unknown", sales: value, })); export const DemoDefaultExample = () => (
Daily Sales Units sold per month
12.5%
!active || !payload?.length ? null : ( {label} {payload.map((entry) => ( {entry.name ?? "Sales"} {entry.value} units ))} ) } />
); ``` ## Anatomy Import the BarChart component and access all parts using dot notation. ```tsx import { BarChart } from "@thenamespace/uikit"; ; ``` ## Comparison {/* DEMO bar-chart-comparison */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; function Legend({ items, }: { items: ReadonlyArray<{ color: string; label: string }>; }) { return (
{items.map(({ color, label }) => (
{label}
))}
); } export const DemoComparisonExample = () => { const data = [ { current: 120, day: "Mon", previous: 90 }, { current: 180, day: "Tue", previous: 150 }, { current: 150, day: "Wed", previous: 170 }, { current: 210, day: "Thu", previous: 140 }, { current: 190, day: "Fri", previous: 160 }, { current: 80, day: "Sat", previous: 100 }, { current: 60, day: "Sun", previous: 70 }, ]; return (
Weekly Orders This week vs last week
} />
); }; ``` ## Custom Tooltip {/* DEMO bar-chart-custom-tooltip */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; import { ChartTooltip } from "@thenamespace/uikit/chart-tooltip"; const channels = [ { direct: 3200, month: "Jan", online: 4200, retail: 2800 }, { direct: 4100, month: "Feb", online: 5800, retail: 3400 }, { direct: 3800, month: "Mar", online: 4900, retail: 3100 }, { direct: 5200, month: "Apr", online: 7200, retail: 4200 }, { direct: 4600, month: "May", online: 6100, retail: 3800 }, { direct: 5800, month: "Jun", online: 8400, retail: 4500 }, ]; const channelSeries = [ { color: "var(--chart-3)", key: "online", label: "Online" }, { color: "var(--chart-2)", key: "retail", label: "Retail" }, { color: "var(--chart-1)", key: "direct", label: "Direct" }, ] as const; export const DemoCustomTooltipExample = () => ( Revenue by Channel `${(value / 1000).toFixed(0)}k`} width={40} /> {channelSeries.map((series) => ( ))} { if ( !active || !payload?.length || !payload.every((entry) => typeof entry.value === "number") ) return null; const total = payload.reduce( (sum, entry) => sum + Number(entry.value ?? 0), 0, ); return ( {label} {payload.map((entry, index) => ( {entry.name} ${Number(entry.value).toLocaleString()} ))}
Total ${total.toLocaleString()}
); }} />
); ``` ## Grouped {/* DEMO bar-chart-grouped */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; const channels = [ { direct: 3200, month: "Jan", online: 4200, retail: 2800 }, { direct: 4100, month: "Feb", online: 5800, retail: 3400 }, { direct: 3800, month: "Mar", online: 4900, retail: 3100 }, { direct: 5200, month: "Apr", online: 7200, retail: 4200 }, { direct: 4600, month: "May", online: 6100, retail: 3800 }, { direct: 5800, month: "Jun", online: 8400, retail: 4500 }, ]; function Legend({ items, }: { items: ReadonlyArray<{ color: string; label: string }>; }) { return (
{items.map(({ color, label }) => (
{label}
))}
); } const channelSeries = [ { color: "var(--chart-3)", key: "online", label: "Online" }, { color: "var(--chart-2)", key: "retail", label: "Retail" }, { color: "var(--chart-1)", key: "direct", label: "Direct" }, ] as const; export const DemoGroupedExample = () => ( Revenue by Channel `${(value / 1000).toFixed(0)}k`} width={40} /> {channelSeries.map((series) => ( ))} `${Number(value).toLocaleString()}`} /> } /> ); ``` ## Horizontal {/* DEMO bar-chart-horizontal */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; export const DemoHorizontalExample = () => ( Top Products Units sold this quarter } /> ); ``` ## Horizontal Stacked {/* DEMO bar-chart-horizontal-stacked */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; function Legend({ items, }: { items: ReadonlyArray<{ color: string; label: string }>; }) { return (
{items.map(({ color, label }) => (
{label}
))}
); } const energy = [ { day: "Mon", high: 180, low: 120, medium: 280 }, { day: "Tue", high: 220, low: 150, medium: 320 }, { day: "Wed", high: 150, low: 180, medium: 250 }, { day: "Thu", high: 180, low: 140, medium: 290 }, { day: "Fri", high: 190, low: 160, medium: 270 }, { day: "Sat", high: 210, low: 130, medium: 240 }, { day: "Sun", high: 240, low: 170, medium: 300 }, ]; export const DemoHorizontalStackedExample = () => (
Avg. Energy Activity
580/280 kcal
`${value} kcal`} /> } />
); ``` ## KPIWith Bar Chart {/* DEMO bar-chart-kpiwith-bar-chart */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { KPI } from "@thenamespace/uikit/kpi"; import { TrendChip } from "@thenamespace/uikit/trend-chip"; const sales = [18, 32, 28, 45, 38, 52, 42, 55, 48, 60, 53, 58].map((value, index) => ({ month: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][index] ?? "Unknown", sales: value, })); export const DemoKPIWithBarChartExample = () => ( Monthly Sales
3.3% last 30d
} />
); ``` ## Stacked {/* DEMO bar-chart-stacked */} ```tsx "use client"; import { BarChart } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; function Legend({ items, }: { items: ReadonlyArray<{ color: string; label: string }>; }) { return (
{items.map(({ color, label }) => (
{label}
))}
); } const plans = [ { enterprise: 12000, pro: 8000, quarter: "Q1", starter: 4000 }, { enterprise: 15000, pro: 10000, quarter: "Q2", starter: 5000 }, { enterprise: 18000, pro: 12000, quarter: "Q3", starter: 6000 }, { enterprise: 22000, pro: 14000, quarter: "Q4", starter: 7000 }, ]; export const DemoStackedExample = () => ( Revenue by Plan `${(value / 1000).toFixed(0)}k`} width={40} /> `${Number(value).toLocaleString()}`} /> } /> ); ``` ## CSS Classes ### Element Classes - `.bar-chart` — Root container wrapping `ResponsiveContainer` and the Recharts chart. ### Recharts Theming The following CSS rules target Recharts internal class names to apply Namespace UIKit design tokens automatically: - `.bar-chart .recharts-cartesian-axis-tick-value` — Axis tick labels. 10px muted text. - `.bar-chart .recharts-cartesian-axis-line` — Axis lines. Hidden by default. - `.bar-chart .recharts-cartesian-axis-tick-line` — Tick lines. Hidden by default. - `.bar-chart .recharts-cartesian-grid line` — Cartesian grid lines. Muted stroke at 0.15 opacity. - `.bar-chart .recharts-tooltip-cursor` — Tooltip cursor. Subtle filled rectangle behind the hovered bar. ## API Reference ### BarChart The root wrapper. Renders a `ResponsiveContainer` + Recharts `BarChart` with Namespace UIKit CSS theming applied automatically. | Prop | Type | Default | Description | | ---------- | ------------------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------- | | `data` | `Record[]` | — | Chart data — array of objects with numeric/string fields for each series. | | `height` | `number` | `300` | Chart height in pixels. | | `width` | ``number \| `${number}%` `` | `"100%"` | Chart width in pixels or percentage string. | | `layout` | `"horizontal" \| "vertical"` | `"horizontal"` | Bar layout direction. Use `"vertical"` for horizontal bar charts. | | `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `{ top: 8, right: 8, bottom: 0, left: 0 }` | Recharts margin around the chart area. | | `children` | `ReactNode` | — | Recharts child components (`BarChart.Bar`, `BarChart.XAxis`, etc.). | Also supports all native `div` HTML attributes. ### BarChart.Bar Re-exported Recharts `Bar` component. Follows the [Recharts Bar API](https://recharts.github.io/en-US/api/Bar/). ### BarChart.XAxis Re-exported Recharts `XAxis` component. Follows the [Recharts XAxis API](https://recharts.github.io/en-US/api/XAxis/). ### BarChart.YAxis Re-exported Recharts `YAxis` component. Follows the [Recharts YAxis API](https://recharts.github.io/en-US/api/YAxis/). ### BarChart.Grid Re-exported Recharts `CartesianGrid` component. Follows the [Recharts CartesianGrid API](https://recharts.github.io/en-US/api/CartesianGrid/). ### BarChart.Tooltip Re-exported Recharts `Tooltip` component. Follows the [Recharts Tooltip API](https://recharts.github.io/en-US/api/Tooltip/). Use with `BarChart.TooltipContent` for styled tooltips. ### BarChart.TooltipContent Pre-built tooltip renderer for Recharts. Pass as the `content` prop of `BarChart.Tooltip`. See [ChartTooltip](/react/components/chart-tooltip) for full props.
# Breadcrumbs **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/breadcrumbs **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/breadcrumbs.mdx > Navigation breadcrumbs showing the current page's location within a hierarchy ## Import ```tsx import { Breadcrumbs } from "@thenamespace/uikit"; ``` ### Usage ```tsx "use client"; import {Breadcrumbs} from "@thenamespace/uikit"; export default function BreadcrumbsBasic() { return ( Home Products Electronics Laptop ); } ``` ### Anatomy Import the Breadcrumbs component and access all parts using dot notation. ```tsx import { Breadcrumbs } from "@thenamespace/uikit"; export default () => ( Home Category Current Page ); ``` ### Navigation Levels ```tsx "use client"; import {Breadcrumbs} from "@thenamespace/uikit"; export default function BreadcrumbsLevel2() { return ( Home Current Page ); } ``` ```tsx "use client"; import {Breadcrumbs} from "@thenamespace/uikit"; export default function BreadcrumbsLevel3() { return ( Home Category Current Page ); } ``` ### Custom Separator ```tsx "use client"; import {Breadcrumbs} from "@thenamespace/uikit"; export default function BreadcrumbsCustomSeparator() { return ( } > Home Products Electronics Laptop ); } ``` ### Disabled State ```tsx "use client"; import {Breadcrumbs} from "@thenamespace/uikit"; export default function BreadcrumbsDisabled() { return ( Home Products Electronics Laptop ); } ``` ### Custom Render Function ```tsx "use client"; import {Breadcrumbs} from "@thenamespace/uikit"; export function CustomRenderFunction() { return (
    }>
  1. }> Home
  2. }> Products
  3. }> Electronics
  4. }> Laptop ); } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { Breadcrumbs } from "@thenamespace/uikit"; function CustomBreadcrumbs() { return ( Home Current ); } ``` ### Customizing the component classes To customize the Breadcrumbs component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .breadcrumbs { @apply gap-4 text-lg; } .breadcrumbs__link { @apply font-semibold; } .breadcrumbs__separator { @apply text-blue-500; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The Breadcrumbs component uses these CSS classes: #### Base Classes - `.breadcrumbs` - Base breadcrumbs container - `.breadcrumbs__item` - Individual breadcrumb item wrapper - `.breadcrumbs__link` - Breadcrumb link element - `.breadcrumbs__separator` - Separator icon between items #### State Classes - `.breadcrumbs__link[data-current="true"]` - Current page indicator (not a link) ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: - **Current**: `[data-current="true"]` on link (indicates current page) - **Hover**: Link elements support standard hover states - **Disabled**: `isDisabled` prop disables all links ## API Reference ### Breadcrumbs Props | Prop | Type | Default | Description | | ------------ | ----------------------------------------------------------------- | ------------------ | --------------------------------------------------------------- | | `separator` | `ReactNode` | chevron-right icon | Custom separator between breadcrumb items | | `isDisabled` | `boolean` | `false` | Whether all breadcrumb links are disabled | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | The breadcrumb items | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function | ### Breadcrumbs.Item Props | Prop | Type | Default | Description | | ----------- | ----------------------------------------------------------------------------- | ------- | --------------------------------------------------------------- | | `href` | `string` | - | The URL to link to (omit for current page) | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Item content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function | ## Accessibility Breadcrumbs uses React Aria Components' Breadcrumbs primitive, which provides: - Proper ARIA attributes for navigation landmarks - Current page indication via `aria-current="page"` - Keyboard navigation support - Screen reader announcements for navigation context The last breadcrumb item (without `href`) automatically becomes the current page indicator. # ButtonGroup **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/button-group **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/button-group.mdx > Group related buttons together with consistent styling and spacing ## Import ```tsx import { ButtonGroup, Button } from "@thenamespace/uikit"; ``` ### Usage ```tsx import { Button, ButtonGroup, Chip, Description, Dropdown, Label, } from "@thenamespace/uikit"; import { ArrowDown01Icon, ArrowLeft01Icon, ArrowRight01Icon, GitForkIcon, MoreHorizontalIcon, Image01Icon, PinIcon, QrCodeIcon, StarIcon, TextAlignCenterIcon, TextAlignJustifyCenterIcon, TextAlignLeftIcon, TextAlignRightIcon, ThumbsDownIcon, ThumbsUpIcon, Video01Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; export function Basic() { return (
    {/* Single button with dropdown */}
    All commits from this branch will be added to the base branch The 14 commits from this branch will be combined into one commit in the base branch The 14 commits from this branch will be rebased and added to the base branch
    {/* Individual buttons */}
    {/* Previous/Next Button Group */}
    {/* Content Selection Button Group */}
    {/* Text Alignment Button Group */}
    {/* Icon-Only Alignment Button Group */}
    ); } ``` ### Anatomy Import the ButtonGroup component and access all parts using dot notation. ```tsx import { ButtonGroup, Button } from "@thenamespace/uikit"; export default () => ( ); ``` > **ButtonGroup** wraps multiple Button components together, applying consistent styling, spacing, and automatic border radius handling. It uses React Context to pass `size`, `variant`, and `isDisabled` props to all child buttons. ### Variants ```tsx import {Button, ButtonGroup} from "@thenamespace/uikit"; export function Variants() { return (

    Primary

    Secondary

    Tertiary

    Outline

    Ghost

    Danger

    ); } ``` ### Sizes ```tsx import {Button, ButtonGroup} from "@thenamespace/uikit"; export function Sizes() { return (

    Small

    Medium (default)

    Large

    ); } ``` ### Orientation Use the `orientation` prop to arrange buttons horizontally or vertically. ```tsx import { Button, ButtonGroup } from "@thenamespace/uikit"; import { TextAlignCenterIcon, TextAlignJustifyCenterIcon, TextAlignLeftIcon, TextAlignRightIcon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; export function Orientation() { return (
    Horizontal
    Vertical
    ); } ``` ### With Icons ```tsx import { Button, ButtonGroup } from "@thenamespace/uikit"; import { Globe02Icon, Add01Icon, Delete02Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; export function WithIcons() { return (

    With icons

    Icon only buttons

    ); } ``` ### Full Width ```tsx import { Button, ButtonGroup } from "@thenamespace/uikit"; import { TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; export function FullWidth() { return (
    ); } ``` ### Disabled State ```tsx import {Button, ButtonGroup} from "@thenamespace/uikit"; export function Disabled() { return (

    All buttons disabled

    Group disabled, but one button overrides

    ); } ``` ### Without Separator Simply omit the `` component from your buttons. ```tsx import {Button, ButtonGroup} from "@thenamespace/uikit"; export function WithoutSeparator() { return ( ); } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { ButtonGroup, Button } from "@thenamespace/uikit"; function CustomButtonGroup() { return ( ); } ``` ### Customizing the component classes To customize the ButtonGroup component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .button-group { @apply gap-2 rounded-lg; } .button-group__separator { @apply opacity-25; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The ButtonGroup component uses these CSS classes: #### Base Classes - `.button-group` - Base button group container - `.button-group--full-width` - Full width modifier - `.button-group__separator` - Separator element between buttons The ButtonGroup component automatically applies border radius to buttons: - First button gets rounded left/start edge - Last button gets rounded right/end edge - Middle buttons have no border radius - Single button gets full border radius on all edges Add `` inside each Button (except the first) to show dividers between buttons. ## API Reference ### ButtonGroup Props | Prop | Type | Default | Description | | ------------- | --------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | `variant` | `'primary' \| 'secondary' \| 'tertiary' \| 'ghost' \| 'danger'` | - | Visual style variant applied to all buttons in the group | | `size` | `'sm' \| 'md' \| 'lg'` | - | Size applied to all buttons in the group | | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | The orientation of the button group | | `fullWidth` | `boolean` | `false` | Whether the button group should take full width of its container | | `isDisabled` | `boolean` | `false` | Whether all buttons in the group are disabled (can be overridden on individual buttons) | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Button components to group together | ### ButtonGroup.Separator Props | Prop | Type | Default | Description | | ----------- | -------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | ### Notes - ButtonGroup uses React Context to pass `size`, `variant`, and `isDisabled` props to all child Button components - **Only direct child buttons receive the ButtonGroup props** - Buttons nested inside other components (like Modal, Dropdown, etc.) will not inherit the group's props even if they are descendants of the ButtonGroup - Individual Button components can override the group's `isDisabled` prop by setting `isDisabled={false}` - The component automatically handles border radius between buttons - Add `` inside each Button (except the first) to show dividers between buttons - Buttons in a group have their active/pressed scale transform removed for a more cohesive appearance
    # Button **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/button **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/button.mdx > A clickable button component with multiple variants and states ## Import ```tsx import { Button } from "@thenamespace/uikit"; ``` ### Usage ```tsx "use client"; import {Button} from "@thenamespace/uikit"; export function Basic() { return ; } ``` ### Variants ```tsx import {Button} from "@thenamespace/uikit"; export function Variants() { return (
    ); } ``` ### With Icons ```tsx import { Button } from "@thenamespace/uikit"; import { Mail01Icon, Globe02Icon, Add01Icon, Delete02Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; export function WithIcons() { return (
    ); } ``` ### Icon Only ```tsx import { Button } from "@thenamespace/uikit"; import { MoreHorizontalIcon, Settings01Icon, Delete02Icon, HugeiconsIcon, } from "@thenamespace/uikit/icons"; export function IconOnly() { return (
    ); } ``` ### Loading ```tsx "use client"; import {Button, Spinner} from "@thenamespace/uikit"; import React from "react"; export function Loading() { return ( ); } ``` ### Loading State ```tsx "use client"; import React, { useState } from "react"; import { Button, Spinner } from "@thenamespace/uikit"; import { Attachment01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function LoadingState() { const [isLoading, setLoading] = useState(false); const handlePress = () => { setLoading(true); setTimeout(() => setLoading(false), 2000); }; return ( ); } ``` ### Sizes ```tsx import {Button} from "@thenamespace/uikit"; export function Sizes() { return (
    ); } ``` ### Full Width ```tsx import { Button } from "@thenamespace/uikit"; import { Add01Icon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function FullWidth() { return (
    ); } ``` ### Disabled State ```tsx import {Button} from "@thenamespace/uikit"; export function Disabled() { return (
    ); } ``` ### Social Buttons ```tsx import { Button } from "@thenamespace/uikit"; import { Icon } from "@/demos/icon"; export function Social() { return (
    ); } ``` ### Custom Render Function ```tsx "use client"; import {Button} from "@thenamespace/uikit"; export function CustomRenderFunction() { return ( ); } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { Button } from "@thenamespace/uikit"; function CustomButton() { return ( ); } ``` ### Customizing the component classes To customize the Button component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .button { @apply bg-purple-500 text-white hover:bg-purple-600; } .button--icon-only { @apply rounded-lg bg-blue-500; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### Adding custom variants You can extend Namespace UIKit components by wrapping them and adding your own custom variants. ```tsx import type {ButtonProps} from "@thenamespace/uikit"; import type {VariantProps} from "tailwind-variants"; import {Button, buttonVariants} from "@thenamespace/uikit"; import {tv} from "tailwind-variants"; const myButtonVariants = tv({ base: "text-md font-semibold shadow-md text-shadow-lg data-[pending=true]:opacity-40", defaultVariants: { radius: "full", variant: "primary", }, extend: buttonVariants, variants: { radius: { full: "rounded-full", lg: "rounded-lg", md: "rounded-md", sm: "rounded-sm", }, size: { lg: "h-12 px-8", md: "h-11 px-6", sm: "h-10 px-4", xl: "h-13 px-10", }, variant: { primary: "text-white dark:bg-white/10 dark:text-white dark:hover:bg-white/15", }, }, }); type MyButtonVariants = VariantProps; export type MyButtonProps = Omit & MyButtonVariants & {className?: string}; function CustomButton({className, radius, variant, ...props}: MyButtonProps) { return ); } ``` ### CSS Classes The Button component uses these CSS classes: #### Base & Size Classes - `.button` - Base button styles - `.button--sm` - Small size variant - `.button--md` - Medium size variant - `.button--lg` - Large size variant #### Variant Classes - `.button--primary` - `.button--secondary` - `.button--tertiary` - `.button--outline` - `.button--ghost` - `.button--danger` #### Modifier Classes - `.button--icon-only` - `.button--icon-only.button--sm` - `.button--icon-only.button--lg` ### Interactive States The button supports both CSS pseudo-classes and data attributes for flexibility: - **Hover**: `:hover` or `[data-hovered="true"]` - **Active/Pressed**: `:active` or `[data-pressed="true"]` (includes scale transform) - **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring) - **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events) - **Pending**: `[data-pending]` (no pointer events during loading) ## API Reference ### Button Props | Prop | Type | Default | Description | | ------------ | ---------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | | `variant` | `'primary' \| 'secondary' \| 'tertiary' \| 'outline' \| 'ghost' \| 'danger'` | `'primary'` | Visual style variant | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the button | | `fullWidth` | `boolean` | `false` | Whether the button should take full width of its container | | `isDisabled` | `boolean` | `false` | Whether the button is disabled | | `isPending` | `boolean` | `false` | Whether the button is in a loading state | | `isIconOnly` | `boolean` | `false` | Whether the button contains only an icon | | `onPress` | `(e: PressEvent) => void` | - | Handler called when the button is pressed | | `children` | `React.ReactNode \| (values: ButtonRenderProps) => React.ReactNode` | - | Button content or render prop | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### ButtonRenderProps When using the render prop pattern, these values are provided: | Prop | Type | Description | | ---------------- | --------- | ---------------------------------------------- | | `isPending` | `boolean` | Whether the button is in a loading state | | `isPressed` | `boolean` | Whether the button is currently pressed | | `isHovered` | `boolean` | Whether the button is hovered | | `isFocused` | `boolean` | Whether the button is focused | | `isFocusVisible` | `boolean` | Whether the button should show focus indicator | | `isDisabled` | `boolean` | Whether the button is disabled |
    # Calendar Year Picker **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/calendar-year-picker **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/calendar-year-picker.mdx > Year-selection parts shared by Calendar and RangeCalendar. Import this public entry point directly when composing lower-level UIKit parts. ```tsx import * as CalendarYearPicker from "@thenamespace/uikit/calendar-year-picker"; ``` See [calendar](./calendar) for complete composition examples and API guidance. This entry point is also re-exported from `@thenamespace/uikit`. Direct imports make the dependency explicit and provide a stable location for advanced customization. # Calendar **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/calendar **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/calendar.mdx > Composable date picker with month grid, navigation, and year picker support built on React Aria Calendar ## Import ```tsx import { Calendar } from "@thenamespace/uikit"; ``` ### Usage ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; export function Basic() { return ( {(day) => {day}} {(date) => } ); } ``` ### Anatomy ```tsx import { Calendar } from "@thenamespace/uikit"; export default () => ( {(day) => {day}} {(date) => } ); ``` ### Year Picker `Calendar.YearPickerTrigger`, `Calendar.YearPickerGrid`, and their body/cell subcomponents provide an integrated year navigation pattern. ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; export function YearPicker() { return ( {(day) => {day}} {(date) => } {({year}) => } ); } ``` ### Default Value ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; import {parseDate} from "@internationalized/date"; export function DefaultValue() { return ( {(day) => {day}} {(date) => } ); } ``` ### Controlled Use controlled `value` and `focusedValue` for external state coordination and custom shortcuts. ```tsx "use client"; import type {CalendarDate} from "@internationalized/date"; import {Button, ButtonGroup, Calendar, Description} from "@thenamespace/uikit"; import { getLocalTimeZone, parseDate, startOfMonth, startOfWeek, today, } from "@internationalized/date"; import {useState} from "react"; import {useLocale} from "react-aria-components"; export function Controlled() { const [value, setValue] = useState(null); const [focusedDate, setFocusedDate] = useState(parseDate("2025-12-25")); const {locale} = useLocale(); return (
    {(day) => {day}} {(date) => } Selected date: {value ? value.toString() : "(none)"}
    ); } ``` ### Min and Max Dates ```tsx "use client"; import {Calendar, Description} from "@thenamespace/uikit"; import {getLocalTimeZone, today} from "@internationalized/date"; export function MinMaxDates() { const now = today(getLocalTimeZone()); const minDate = now; const maxDate = now.add({months: 3}); return (
    {(day) => {day}} {(date) => } Select a date between today and {maxDate.toString()}
    ); } ``` ### Unavailable Dates Use `isDateUnavailable` to block dates such as weekends, holidays, or booked slots. ```tsx "use client"; import type {DateValue} from "@internationalized/date"; import {Calendar, Description} from "@thenamespace/uikit"; import {isWeekend} from "@internationalized/date"; import {useLocale} from "react-aria-components"; export function UnavailableDates() { const {locale} = useLocale(); const isDateUnavailable = (date: DateValue) => isWeekend(date, locale); return (
    {(day) => {day}} {(date) => } Weekends are unavailable
    ); } ``` ### Weeks in Month Set `weeksInMonth` to a fixed value (for example, `6`) to keep the grid height stable when navigating between months. Use with care in non-Gregorian locales, similar to `firstDayOfWeek`. ```tsx "use client"; import {Calendar, Description} from "@thenamespace/uikit"; export function WeeksInMonth() { return (
    {(day) => {day}} {(date) => } Always shows 6 weeks per month to avoid layout shift when navigating
    ); } ``` ### Week View Set `visibleDuration={{ weeks: n }}` to show one or more weeks at a time. Navigation advances by the visible week range. Use `pageBehavior="single"` to move one week at a time when showing multiple weeks. ```tsx "use client"; import {Calendar, Label, ListBox, Select} from "@thenamespace/uikit"; import {useState} from "react"; const weekOptions = [ {id: "1", name: "1 week"}, {id: "2", name: "2 weeks"}, {id: "3", name: "3 weeks"}, {id: "4", name: "4 weeks"}, {id: "5", name: "5 weeks"}, {id: "6", name: "6 weeks"}, {id: "8", name: "8 weeks"}, ] as const; export function WeekView() { const [weeks, setWeeks] = useState(1); return (
    {(day) => {day}} {(date) => }
    ); } ``` ### Day View Set `visibleDuration={{ days: n }}` to show a rolling window of consecutive days. Navigation advances by the visible day range. Use `pageBehavior="single"` to move one day at a time when showing multiple days. ```tsx "use client"; import {Calendar, Label, ListBox, Select} from "@thenamespace/uikit"; import {useState} from "react"; const dayOptions = [ {id: "1", name: "1 day"}, {id: "5", name: "5 days"}, {id: "7", name: "7 days"}, {id: "8", name: "8 days"}, {id: "10", name: "10 days"}, {id: "14", name: "14 days"}, {id: "21", name: "21 days"}, ] as const; export function DayView() { const [days, setDays] = useState(5); return (
    {(day) => {day}} {(date) => }
    ); } ``` ### Multiple Selection Set `selectionMode="multiple"` to let users select several dates. `value`, `defaultValue`, and `onChange` use an array of dates. ```tsx "use client"; import type {DateValue} from "@internationalized/date"; import {Calendar, Description} from "@thenamespace/uikit"; import {useState} from "react"; export function MultipleSelection() { const [value, setValue] = useState([]); return (
    {(day) => {day}} {(date) => } {value?.length ? `${value.length} date(s) selected` : "Select multiple dates"}
    ); } ``` ### Disabled ```tsx "use client"; import {Calendar, Description} from "@thenamespace/uikit"; import {getLocalTimeZone, today} from "@internationalized/date"; export function Disabled() { return (
    {(day) => {day}} {(date) => } Calendar is disabled
    ); } ``` ### Read Only ```tsx "use client"; import {Calendar, Description} from "@thenamespace/uikit"; import {getLocalTimeZone, today} from "@internationalized/date"; export function ReadOnly() { return (
    {(day) => {day}} {(date) => } Calendar is read-only
    ); } ``` ### Focused Value Programmatically control which date is focused using `focusedValue` and `onFocusChange`. ```tsx "use client"; import type {DateValue} from "@internationalized/date"; import {Button, Calendar, Description} from "@thenamespace/uikit"; import {parseDate} from "@internationalized/date"; import {useState} from "react"; export function FocusedValue() { const [focusedDate, setFocusedDate] = useState(parseDate("2025-06-15")); return (
    {(day) => {day}} {(date) => } Focused: {focusedDate.toString()}
    ); } ``` ### Cell Indicators You can customize `Calendar.Cell` children and use `Calendar.CellIndicator` to display metadata like events. ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; import {getLocalTimeZone, isToday} from "@internationalized/date"; const datesWithEvents = [3, 7, 12, 15, 21, 28]; export function WithIndicators() { return ( {(day) => {day}} {(date) => ( {({formattedDate}) => ( <> {formattedDate} {(isToday(date, getLocalTimeZone()) || datesWithEvents.includes(date.day)) && ( )} )} )} ); } ``` ### Multiple Months Render multiple grids with `visibleDuration` and `offset` for booking and planning experiences. Use `Calendar.Heading` with an `offset` (for example, `offset={{ months: 1 }}`) in each column header to label that month. ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; export function MultipleMonths() { return (
    {(day) => {day}} {(date) => }
    {(day) => {day}} {(date) => }
    ); } ``` ### International Calendars By default, Calendar displays dates using the calendar system for the user's locale. You can override this by wrapping your Calendar with `I18nProvider` and setting the [Unicode calendar locale extension](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string). The example below shows the Indian calendar system: ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; import {getLocalTimeZone, today} from "@internationalized/date"; import {I18nProvider} from "react-aria-components"; export function InternationalCalendar() { return ( {(day) => {day}} {(date) => } {({year}) => } ); } ``` **Note:** The `onChange` event always returns a date in the same calendar system as the `value` or `defaultValue` (Gregorian if no value is provided), regardless of the displayed locale. This ensures your application logic works consistently with a single calendar system while still displaying dates in the user's preferred format. ### Custom Navigation Icons Pass children to `Calendar.NavButton` to replace the default chevron icons. ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; export function CustomIcons() { return ( {(day) => {day}} {(date) => } ); } ``` ### Real-World Example ```tsx "use client"; import type {CalendarDate, DateValue} from "@internationalized/date"; import {Button, Calendar} from "@thenamespace/uikit"; import {getLocalTimeZone, isWeekend, today} from "@internationalized/date"; import {useState} from "react"; import {useLocale} from "react-aria-components"; export function BookingCalendar() { const [selectedDate, setSelectedDate] = useState(null); const {locale} = useLocale(); const bookedDates = [5, 6, 12, 13, 14, 20]; const isDateUnavailable = (date: DateValue) => { return isWeekend(date, locale) || bookedDates.includes(date.day); }; return (
    {(day) => {day}} {(date) => ( {({formattedDate, isUnavailable}) => ( <> {formattedDate} {!isUnavailable && !isWeekend(date, locale) && bookedDates.includes(date.day) && } )} )}
    Has bookings Weekend/Unavailable
    {selectedDate ? ( ) : null}
    ); } ``` ### Custom Styles ```tsx "use client"; import {Calendar} from "@thenamespace/uikit"; export function CustomStyles() { return ( {(day) => {day}} {(date) => } {({year}) => } ); } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import { Calendar } from "@thenamespace/uikit"; function CustomCalendar() { return ( {(day) => {day}} {(date) => } ); } ``` ### Customizing the component classes ```css @layer components { .calendar { @apply w-72 rounded-2xl border border-border bg-surface p-3 shadow-sm; } .calendar__heading { @apply text-sm font-semibold text-default-700; } .calendar__cell[data-selected="true"] { @apply bg-accent text-accent-foreground; } } ``` ### CSS Classes Calendar uses these classes in `packages/styles/components/calendar.css` and `packages/styles/components/calendar-year-picker.css`: - `.calendar` - Root container. - `.calendar__header` - Header row containing nav buttons and heading. - `.calendar__heading` - Current month label. - `.calendar__nav-button` - Previous/next navigation controls. - `.calendar__grid` - Main day grid. - `.calendar__grid-header` - Weekday header row wrapper. - `.calendar__grid-body` - Date rows wrapper. - `.calendar__header-cell` - Weekday header cell. - `.calendar__cell` - Interactive day cell. - `.calendar__cell-indicator` - Dot indicator inside a day cell. - `.calendar-year-picker__trigger` - Year picker toggle button. - `.calendar-year-picker__trigger-heading` - Heading text inside year picker trigger. - `.calendar-year-picker__trigger-indicator` - Indicator icon inside year picker trigger. - `.calendar-year-picker__year-grid` - Overlay grid of selectable years. - `.calendar-year-picker__year-cell` - Individual year option. ### Interactive States Calendar supports both pseudo-classes and React Aria data attributes: - **Selected**: `[data-selected="true"]` - **Today**: `[data-today="true"]` - **Unavailable**: `[data-unavailable="true"]` - **Outside month**: `[data-outside-month="true"]` - **Hovered**: `:hover` or `[data-hovered="true"]` - **Pressed**: `:active` or `[data-pressed="true"]` - **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]` - **Disabled**: `:disabled` or `[data-disabled="true"]` ## API Reference ### Calendar Props Calendar inherits all props from React Aria [Calendar](https://react-spectrum.adobe.com/react-aria/Calendar.html). | Prop | Type | Default | Description | | ------------------------ | ---------------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `selectionMode` | `'single' \| 'multiple'` | `'single'` | Whether one or many dates can be selected. | | `value` | `DateValue \| null` or `DateValue[] \| null` | - | Controlled selected date(s). Use an array when `selectionMode` is `multiple`. | | `defaultValue` | `DateValue \| null` or `DateValue[] \| null` | - | Initial selected date(s) (uncontrolled). | | `onChange` | `(value: DateValue \| null)` or `(value: DateValue[] \| null) => void` | - | Called when selection changes. | | `focusedValue` | `DateValue` | - | Controlled focused date. | | `onFocusChange` | `(value: DateValue) => void` | - | Called when focus moves to another date. | | `minValue` | `DateValue` | Calendar-aware `1900-01-01` | Earliest selectable date. | | `maxValue` | `DateValue` | Calendar-aware `2099-12-31` | Latest selectable date. | | `weeksInMonth` | `number` | - | The number of weeks in a month. This overrides the default set by the locale. | | `isDateUnavailable` | `(date: DateValue) => boolean` | - | Marks dates as unavailable. | | `firstDayOfWeek` | `'sun' \| 'mon' \| 'tue' \| 'wed' \| 'thu' \| 'fri' \| 'sat'` | - | Overrides the locale default for the first day of the week. | | `pageBehavior` | `'visible' \| 'single'` | `'visible'` | Whether paging advances by the visible duration or one unit at a time. | | `selectionAlignment` | `'start' \| 'center' \| 'end'` | `'center'` | Aligns the visible range to the selection on initial render. | | `isDisabled` | `boolean` | `false` | Disables interaction and selection. | | `isReadOnly` | `boolean` | `false` | Keeps content readable but prevents selection changes. | | `isInvalid` | `boolean` | `false` | Marks the calendar as invalid for validation UI. | | `visibleDuration` | `{months?: number; weeks?: number; days?: number}` | `{months: 1}` | Visible time range. Use `{ months: n }` for month view, `{ weeks: n }` for week view, or `{ days: n }` for day view. | | `defaultYearPickerOpen` | `boolean` | `false` | Initial open state of internal year picker. | | `isYearPickerOpen` | `boolean` | - | Controlled year picker open state. | | `onYearPickerOpenChange` | `(isOpen: boolean) => void` | - | Called when year picker open state changes. | ### Composition Parts | Component | Description | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `Calendar.Header` | Header container for navigation and heading. | | `Calendar.Heading` | Formatted heading for the visible range. Supports `offset` (for multi-month layouts) and `format` (month/year/day options). | | `Calendar.NavButton` | Previous/next navigation control (`slot=\"previous\"` or `slot=\"next\"`). | | `Calendar.Grid` | Day grid for one month (`offset` supported for multi-month layouts). | | `Calendar.GridHeader` | Weekday header container. | | `Calendar.GridBody` | Date cell body container. | | `Calendar.HeaderCell` | Weekday label cell. | | `Calendar.Cell` | Individual date cell. | | `Calendar.CellIndicator` | Optional indicator element for custom metadata. | | `Calendar.YearPickerTrigger` | Trigger to toggle year-picker mode. | | `Calendar.YearPickerTriggerHeading` | Localized heading content inside the year-picker trigger. | | `Calendar.YearPickerTriggerIndicator` | Toggle icon inside the year-picker trigger. | | `Calendar.YearPickerGrid` | Overlay year selection grid container. | | `Calendar.YearPickerGridBody` | Body renderer for year grid cells. | | `Calendar.YearPickerCell` | Individual year option cell. | ### Year Picker Parts Year picker subcomponents inherit formatting props from React Aria [`useCalendarHeading`](https://react-aria.adobe.com/useCalendar#usecalendarheading) and [`useCalendarYearPicker`](https://react-aria.adobe.com/useCalendar#usecalendaryearpicker). | Component | Prop | Type | Default | Description | | ----------------------------------- | -------------- | ---------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `Calendar.YearPickerTriggerHeading` | `format` | `DateFormatterOptions` | - | Customize month/year label (e.g. `{month: 'short'}`). | | `Calendar.YearPickerTriggerHeading` | `offset` | `{months?: number}` | - | Shift the heading relative to the focused date (multi-month layouts). | | `Calendar.YearPickerGrid` | `format` | `DateFormatterOptions` | `{year: 'numeric'}` | Customize year cell labels (era, calendar system, etc.). | | `Calendar.YearPickerGrid` | `visibleYears` | `number` | min–max span or `20` | Number of years shown in the sliding window. Defaults to the full range between `minValue` and `maxValue` when both are set. | ### Calendar.Cell Render Props When `Calendar.Cell` children is a function, React Aria render props are available: | Prop | Type | Description | | ---------------- | --------- | ------------------------------------------- | | `formattedDate` | `string` | Localized day label for the cell. | | `isSelected` | `boolean` | Whether the date is selected. | | `isUnavailable` | `boolean` | Whether the date is unavailable. | | `isDisabled` | `boolean` | Whether the cell is disabled. | | `isOutsideMonth` | `boolean` | Whether the date belongs to adjacent month. | For a complete list of supported calendar systems and their identifiers, see: - [React Aria Calendar Implementations](https://react-aria.adobe.com/internationalized/date/Calendar#implementations) - [React Aria International Calendars](https://react-aria.adobe.com/Calendar#international-calendars) ### Related packages - [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — date types (`CalendarDate`, `CalendarDateTime`, `ZonedDateTime`) and utilities used by all date components - [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree - [`useLocale`](https://react-aria.adobe.com/useLocale) — read the current locale and layout direction # Card **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/card **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/card.mdx > Flexible container component for grouping related content and actions ## Import ```tsx import { Card } from "@thenamespace/uikit"; ``` ### Usage ```tsx import { Card, Link } from "@thenamespace/uikit"; import { DollarCircleIcon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function Default() { return ( Become an Acme Creator! Visit the Acme Creator Hub to sign up today and start earning credits from your fans and followers. Creator Hub ); } ``` ### Anatomy Import the Card component and access all parts using dot notation. ```tsx import { Card } from "@thenamespace/uikit"; export default () => ( ); ``` ### Variants Cards come in semantic variants that describe their prominence level rather than specific visual styles. This allows themes to interpret them differently: ```tsx import {Card} from "@thenamespace/uikit"; export function Variants() { return (
    Transparent Minimal prominence with transparent background

    Use for less important content or nested cards

    Default Standard card appearance (bg-surface)

    The default card variant for most use cases

    Secondary Medium prominence (bg-surface-secondary)

    Use to draw moderate attention

    Tertiary Higher prominence (bg-surface-tertiary)

    Use for primary or featured content

    ); } ``` - **`transparent`** - Minimal prominence, transparent background (great for nested cards) - **`default`** - Standard card for most use cases (surface-secondary) - **`secondary`** - Medium prominence to draw moderate attention (surface-tertiary) - **`tertiary`** - Higher prominence for important content (surface-tertiary) ### Horizontal Layout ```tsx import {Button, Card, CloseButton} from "@thenamespace/uikit"; export function Horizontal() { return (
    Cherries
    Become an ACME Creator! Lorem ipsum dolor sit amet consectetur. Sed arcu donec id aliquam dolor sed amet faucibus etiam.
    Only 10 spots Submission ends Oct 10.
    ); } ``` ### With Avatar ```tsx import {Avatar, Card} from "@thenamespace/uikit"; export function WithAvatar() { return (
    Indie Hackers community Indie Hackers 148 members IH By Martha AI Builders community AI Builders 362 members B By John
    ); } ``` ### With Images ```tsx import { Avatar, Button, Card, CloseButton, Link } from "@thenamespace/uikit"; import { DollarCircleIcon, HugeiconsIcon } from "@thenamespace/uikit/icons"; export function WithImages() { return (
    {/* Row 1: Large Product Card - Available Soon */}
    Cherries
    Become an ACME Creator! Lorem ipsum dolor sit amet consectetur. Sed arcu donec id aliquam dolor sed amet faucibus etiam.
    Only 10 spots Submission ends Oct 10.
    {/* Row 2 */}
    {/* Left Column */}
    {/* Top Card */}
    PAYMENT You can now withdraw on crypto Add your wallet in settings to withdraw
    Go to settings
    {/* Bottom cards */}
    {/* Left Card */} JK

    Indie Hackers

    148 members

    JK

    By John

    {/* Right Card */} AB

    AI Builders

    362 members

    M

    By Martha

    {/* Right Column */} {/* Background image */} {/* Header */} NEO Home Robot {/* Footer */}
    Available soon
    Get notified
    {/* Row 3 */}
    {/* Left Column: Card */}
    NEO
    $499/m
    {/* Right Column: Cards Stack */}
    {/* 1 */} Futuristic Robot
    Bridging the Future Today, 6:30 PM
    {/* 2 */} Avocado
    Avocado Hackathon Wed, 4:30 PM
    {/* 3 */} Sound Electro event
    Sound Electro | Beyond art Fri, 8:00 PM
    ); } ``` ### With Form ```tsx "use client"; import {Button, Card, Form, Input, Label, Link, TextField} from "@thenamespace/uikit"; export function WithForm() { const onSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const data: Record = {}; // Convert FormData to plain object formData.forEach((value, key) => { data[key] = value.toString(); }); alert("Form submitted successfully!"); }; return ( Login Enter your credentials to access your account
    Forgot password?
    ); } ``` ## Accessibility ```tsx import { Card } from '@thenamespace/uikit'; import { cardVariants } from '@thenamespace/uikit/styles'; // Semantic markup Article Title // Interactive cards Product Name ``` ## Styling ### Component Customization ```tsx Custom Styled Card Custom colors applied

    Content with custom styling

    ``` ### CSS Variable Overrides ```css /* Override specific variants */ .card--secondary { @apply bg-gradient-to-br from-blue-50 to-purple-50; } /* Custom element styles */ .card__title { @apply text-xl font-bold; } ``` ## CSS Classes Card uses [BEM](https://getbem.com/) naming for predictable styling,: #### Base Classes - `.card` - Base container with padding and border - `.card__header` - Header section container - `.card__title` - Title with base font size and weight - `.card__description` - Muted description text - `.card__content` - Flexible content container - `.card__footer` - Footer with row layout #### Variant Classes - `.card--transparent` - Minimal prominence, transparent background (maps to `transparent` variant) - `.card--default` - Standard appearance with surface-secondary (default) - `.card--secondary` - Medium prominence with surface-tertiary (maps to `secondary` variant) - `.card--tertiary` - Higher prominence with surface-tertiary (maps to `tertiary` variant) ## API Reference ### Card | Prop | Type | Default | Description | | ----------- | --------------------------------------------------------- | ----------- | -------------------------------------------- | | `variant` | `"transparent" \| "default" \| "secondary" \| "tertiary"` | `"default"` | Semantic variant indicating prominence level | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Card content | ### Card.Header | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Header content | ### Card.Title | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Title content (renders as `h3`) | ### Card.Description | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ------------------------------------ | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Description content (renders as `p`) | ### Card.Content | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Main content | ### Card.Footer | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Footer content |
    # Carousel **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/carousel **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/carousel.mdx > A content browsing component for navigating through a collection of images or items, with thumbnails, dots, and navigation controls. ## Usage {/* DEMO carousel-default */} ```tsx "use client"; import { Carousel } from "@thenamespace/uikit"; const images = [ { alt: "Sneakers front view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/1.jpeg", }, { alt: "Sneakers side view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/2.jpeg", }, { alt: "Sneakers back view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/3.jpeg", }, { alt: "Sneakers top view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/4.jpeg", }, { alt: "Sneakers detail view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/5.jpeg", }, { alt: "Sneakers sole view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/6.jpeg", }, ]; const ImageSlides = () => ( <> {images.map((image) => (
    {image.alt}
    ))} ); export const DemoDefaultExample = () => (
    {images.map((image, index) => ( ))}
    ); ``` ## Anatomy Import the Carousel component and access all parts using dot notation. ```tsx import { Carousel } from "@thenamespace/uikit"; ; ``` ## Modal Type The modal type positions navigation arrows outside the content area, ideal for focused overlay-style viewing. ## Multiple Slides {/* DEMO carousel-multiple-slides */} ```tsx "use client"; import { Carousel } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; export const DemoMultipleSlidesExample = () => (
    {Array.from({ length: 8 }, (_, index) => index + 1).map((number) => (
    {number}
    ))}
    ); ``` Show multiple slides per viewport using Tailwind `basis` utility classes on `Carousel.Item`. ## Infinite Loop {/* DEMO carousel-infinite-loop */} ```tsx "use client"; import { Carousel } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; const NumberSlides = ({ count = 5 }: { count?: number }) => ( <> {Array.from({ length: count }, (_, index) => index + 1).map((number) => (
    {number}
    ))} ); export const DemoInfiniteLoopExample = () => (
    ); ``` Enable infinite looping with `opts={{ loop: true }}`. ## Autoplay {/* DEMO carousel-autoplay-story */} ```tsx "use client"; import { useRef } from "react"; import { Carousel } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; import Autoplay from "embla-carousel-autoplay"; const NumberSlides = ({ count = 5 }: { count?: number }) => ( <> {Array.from({ length: count }, (_, index) => index + 1).map((number) => (
    {number}
    ))} ); export const DemoAutoplayStoryExample = () => { const plugin = useRef(Autoplay({ delay: 2000, stopOnInteraction: true })); return (
    ); }; ``` Use the `embla-carousel-autoplay` plugin via the `plugins` prop. ## API Access {/* DEMO carousel-api-access-story */} ```tsx "use client"; import { useEffect, useState } from "react"; import { Carousel } from "@thenamespace/uikit"; import { Card } from "@thenamespace/uikit/card"; import type { EmblaCarouselType } from "embla-carousel"; const NumberSlides = ({ count = 5 }: { count?: number }) => ( <> {Array.from({ length: count }, (_, index) => index + 1).map((number) => (
    {number}
    ))} ); function ApiExample() { const [api, setApi] = useState(), [current, setCurrent] = useState(1), [count, setCount] = useState(0); useEffect(() => { if (!api) return; const update = () => { setCurrent(api.selectedScrollSnap() + 1); setCount(api.scrollSnapList().length); }; update(); api.on("select", update).on("reInit", update); return () => { api.off("select", update).off("reInit", update); }; }, [api]); return (

    Slide {current} of {count}

    ); } export const DemoApiAccessStoryExample = () => ; ``` Use the `setApi` prop to get the Embla API instance for programmatic control. ### Type: Modal {/* DEMO carousel-modal-type */} ```tsx "use client"; import { Carousel } from "@thenamespace/uikit"; const images = [ { alt: "Sneakers front view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/1.jpeg", }, { alt: "Sneakers side view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/2.jpeg", }, { alt: "Sneakers back view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/3.jpeg", }, { alt: "Sneakers top view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/4.jpeg", }, { alt: "Sneakers detail view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/5.jpeg", }, { alt: "Sneakers sole view", src: "https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/shoes/product-view/6.jpeg", }, ]; const ImageSlides = ({ modal = false }: { modal?: boolean }) => ( <> {images.map((image) => (
    {image.alt}
    ))} ); export const DemoModalTypeExample = () => (
    {images.map((image, index) => ( ))}
    ); ``` ## CSS Classes ### Base Classes - `.carousel` — Root wrapper. Sets `--carousel-gap` for slide spacing. - `.carousel__viewport-wrapper` — Relative positioning context for navigation buttons. - `.carousel__viewport` — Overflow-hidden container that clips off-screen slides. - `.carousel__content` — Flex container holding all slide items. - `.carousel__item` — Individual slide. Min-width zero, flex-shrink zero, full basis by default. ### Type Modifier Classes - `.carousel--in-place` — Default type. Navigation arrows positioned inside the viewport area. - `.carousel--modal` — Overlay-style layout. Arrows positioned far outside the content, flex column with gap. - `.carousel--miniatures` — Compact layout. Arrows inline with the thumbnail row. ### Navigation Button Classes - `.carousel__previous` / `.carousel__next` — Absolute-positioned containers for Namespace UIKit Button (variant `tertiary`, size `sm`, icon-only). - `.carousel__previous--in-place` / `.carousel__next--in-place` — Vertically centered inside the viewport, inset from edges. - `.carousel__previous--modal` / `.carousel__next--modal` — Vertically centered, positioned outside the viewport bounds. - `.carousel__previous--miniatures` / `.carousel__next--miniatures` — Relative positioning (inline with thumbnails). ### Dot Indicator Classes - `.carousel__dots` — Flex container for pagination dots, centered with gap. - `.carousel__dot` — Individual dot. `bg-default` by default, `bg-accent` when selected. Theme-aware border-radius. ### Thumbnail Classes - `.carousel__thumbnails` — Flex container for thumbnail navigation. Centered with gap. - `.carousel__thumbnails--miniatures` — Removes top margin for miniatures type. - `.carousel__thumbnail` — Individual thumbnail button. `size-16`, `rounded-2xl`. Selected state uses `box-shadow` ring with accent color (no layout shift). ### Interactive States - **Hover**: `[data-hovered="true"]` on `.carousel__previous` / `.carousel__next` — applies `bg-default-hover`. - **Pressed**: `[data-pressed="true"]` on `.carousel__previous` / `.carousel__next` — applies `bg-default-hover`. - **Disabled**: `[aria-disabled="true"]` on `.carousel__previous` / `.carousel__next` — applies disabled opacity. - **Focus visible**: `[data-focus-visible="true"]` on buttons, dots, and thumbnails — applies focus ring. - **Dot selected**: `[data-selected="true"]` on `.carousel__dot` — applies `bg-accent`. - **Thumbnail selected**: `[data-selected="true"]` on `.carousel__thumbnail` — applies accent `box-shadow` ring. - **Thumbnail hover**: `[data-hovered="true"]` on `.carousel__thumbnail` — applies `opacity: 0.85`. - **Thumbnail pressed**: `[data-pressed="true"]` on `.carousel__thumbnail` — applies `scale(0.95)`. - **Reduced motion**: `prefers-reduced-motion: reduce` disables all thumbnail transitions. ### CSS Variables - `--carousel-gap` — Spacing between slides (default: `calc(var(--spacing) * 4)`). ## API Reference ### Carousel The root container. Sets up Embla Carousel and provides context to all subcomponents. | Prop | Type | Default | Description | | --------- | --------------------------------------- | ------------ | -------------------------------------------------------------------------------------- | | `opts` | `EmblaOptionsType` | — | Embla Carousel options. See [Embla docs](https://www.embla-carousel.com/api/options/). | | `plugins` | `EmblaPluginType[]` | — | Embla Carousel plugins. See [Embla plugins](https://www.embla-carousel.com/plugins/). | | `type` | `"in-place" \| "modal" \| "miniatures"` | `"in-place"` | Layout type controlling navigation button positioning. | | `setApi` | `(api: EmblaCarouselType) => void` | — | Callback to receive the Embla API instance for programmatic control. | Also supports all HTML `div` props. ### Carousel.Content The scrollable slide container. Renders the Embla viewport wrapper and flex content area. Also supports all HTML `div` props. ### Carousel.Item An individual slide. Set `className="basis-1/3"` (or similar) to show multiple slides per viewport. Also supports all HTML `div` props. ### Carousel.Previous Navigation button to scroll to the previous slide. Automatically disabled when at the start (unless looping). | Prop | Type | Default | Description | | ------ | ----------- | ------- | ------------------------------------------- | | `icon` | `ReactNode` | — | Custom icon to replace the default chevron. | Also supports all HTML `button` props. ### Carousel.Next Navigation button to scroll to the next slide. Automatically disabled when at the end (unless looping). | Prop | Type | Default | Description | | ------ | ----------- | ------- | ------------------------------------------- | | `icon` | `ReactNode` | — | Custom icon to replace the default chevron. | Also supports all HTML `button` props. ### Carousel.Dots Pagination dot indicators. Renders one dot per scroll snap. Automatically hidden when there is only one snap point. | Prop | Type | Default | Description | | ----------- | -------------------------------------------------------------- | ------- | ------------------------------------ | | `renderDot` | `(props: { index: number; isSelected: boolean }) => ReactNode` | — | Custom render function for each dot. | Also supports all HTML `div` props. ### Carousel.Thumbnails Container for thumbnail navigation buttons. Renders as a `tablist`. Also supports all HTML `div` props. ### Carousel.Thumbnail An individual thumbnail button linked to a slide index. Clicking navigates the carousel to that slide. | Prop | Type | Default | Description | | ------- | -------- | ------- | -------------------------------------------------------------------- | | `index` | `number` | — | The slide index this thumbnail navigates to (0-based). Required. | | `src` | `string` | — | Image source URL. Alternatively, pass `children` for custom content. | | `alt` | `string` | `""` | Alt text for the thumbnail image. | Also supports all HTML `button` props. ### useCarousel A hook to access the carousel context from any descendant component. ```tsx const { api, selectedIndex, scrollSnapCount, canScrollPrev, canScrollNext, scrollPrev, scrollNext, scrollTo, } = useCarousel(); ``` **Returns:** | Property | Type | Description | | ----------------- | -------------------------------- | --------------------------------------- | | `api` | `EmblaCarouselType \| undefined` | The Embla API instance. | | `selectedIndex` | `number` | Currently active slide index. | | `scrollSnapCount` | `number` | Total number of scroll snap points. | | `canScrollPrev` | `boolean` | Whether scrolling backward is possible. | | `canScrollNext` | `boolean` | Whether scrolling forward is possible. | | `scrollPrev` | `() => void` | Scroll to the previous slide. | | `scrollNext` | `() => void` | Scroll to the next slide. | | `scrollTo` | `(index: number) => void` | Scroll to a specific slide by index. |
    # Cell Color Picker **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/cell-color-picker **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/cell-color-picker.mdx > A compact color picker styled as a settings cell with preset palettes and custom color input. ## Usage {/* DEMO cell-color-picker-default */} ```tsx "use client"; import { CellColorPicker } from "@thenamespace/uikit"; import { ColorArea } from "@thenamespace/uikit/color-area"; import { ColorSlider } from "@thenamespace/uikit/color-slider"; import { Label } from "@thenamespace/uikit/label"; function PickerControls() { return ( <> ); } function Picker({ label = "Accent", variant = "default", }: { label?: string; variant?: "default" | "secondary"; }) { return ( {label} ); } export const DemoDefaultExample = () => (
    ); ``` ## Anatomy Import the CellColorPicker component and access all parts using dot notation. ```tsx import { CellColorPicker } from "@thenamespace/uikit"; ; ``` ## Controlled {/* DEMO cell-color-picker-controlled */} ```tsx "use client"; import { useState } from "react"; import { CellColorPicker } from "@thenamespace/uikit"; import { ColorArea } from "@thenamespace/uikit/color-area"; import { ColorSlider } from "@thenamespace/uikit/color-slider"; import { Label } from "@thenamespace/uikit/label"; import { parseColor } from "react-aria-components"; function PickerControls() { return ( <> ); } export const DemoControlledExample = function Demo() { const [color, setColor] = useState(parseColor("#3B82F6")); return (
    Accent

    Selected: {color.toString("hex").toUpperCase()}

    ); }; ``` ## Disabled {/* DEMO cell-color-picker-disabled */} ```tsx "use client"; import { CellColorPicker } from "@thenamespace/uikit"; import { ColorArea } from "@thenamespace/uikit/color-area"; import { Label } from "@thenamespace/uikit/label"; export const DemoDisabledExample = () => (
    Accent
    ); ``` ## Settings Group {/* DEMO cell-color-picker-settings-group */} ```tsx "use client"; import { useState } from "react"; import { CellColorPicker } from "@thenamespace/uikit"; import { ColorArea } from "@thenamespace/uikit/color-area"; import { ColorSlider } from "@thenamespace/uikit/color-slider"; import { Label } from "@thenamespace/uikit/label"; import { parseColor } from "react-aria-components"; function PickerControls() { return ( <> ); } export const DemoSettingsGroupExample = function Demo() { const [accent, setAccent] = useState(parseColor("#3B82F6")); const [success, setSuccess] = useState(parseColor("#22C55E")); const [danger, setDanger] = useState(parseColor("#EF4444")); return (
    {[ ["Accent", accent, setAccent], ["Success", success, setSuccess], ["Danger", danger, setDanger], ].map(([label, color, setColor]) => ( {label as string} ))}
    ); }; ``` ## Variants {/* DEMO cell-color-picker-variants */} ```tsx "use client"; import { CellColorPicker } from "@thenamespace/uikit"; import { ColorArea } from "@thenamespace/uikit/color-area"; import { ColorSlider } from "@thenamespace/uikit/color-slider"; import { Label } from "@thenamespace/uikit/label"; function PickerControls() { return ( <> ); } function Picker({ label = "Accent", variant = "default", }: { label?: string; variant?: "default" | "secondary"; }) { return ( {label} ); } export const DemoVariantsExample = () => (
    {(["default", "secondary"] as const).map((variant) => (
    {variant}
    ))}
    ); ``` ## With Presets {/* DEMO cell-color-picker-with-presets */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellColorPicker } from "@thenamespace/uikit"; import { ColorArea } from "@thenamespace/uikit/color-area"; import { ColorSlider } from "@thenamespace/uikit/color-slider"; import { ColorSwatchPicker } from "@thenamespace/uikit/color-swatch-picker"; import { Input } from "@thenamespace/uikit/input"; import { Label } from "@thenamespace/uikit/label"; import { parseColor } from "react-aria-components"; function PickerControls() { return ( <> ); } const presets = [ "#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899", "#f43f5e", ]; export const DemoWithPresetsExample = function Demo() { const [color, setColor] = useState(parseColor("#3B82F6")); return (
    Brand Color {presets.map((preset) => ( ))} #
    ); }; ``` ## CSS Classes ### Base Classes - `.cell-color-picker` - Root wrapper ### Element Classes - `.cell-color-picker__trigger` - The visible cell row button - `.cell-color-picker__trigger--default` - Default variant trigger styling - `.cell-color-picker__trigger--secondary` - Secondary variant trigger styling - `.cell-color-picker__label` - Leading text label - `.cell-color-picker__value-display` - Hex value text display - `.cell-color-picker__swatch` - Color swatch preview - `.cell-color-picker__popover` - Dropdown color picker panel ## API Reference ### CellColorPicker The root component. Wraps Namespace UIKit [ColorPicker](https://namespace.com/docs/react/components/color-picker) with cell-style layout. | Prop | Type | Default | Description | | --------- | -------------------------- | ----------- | -------------------- | | `variant` | `'default' \| 'secondary'` | `'default'` | Visual style variant | Also supports all RAC [ColorPicker](https://react-spectrum.adobe.com/react-aria/ColorPicker.html) props except `children` (`defaultValue`, `value`, `onChange`, etc.). ### CellColorPicker.Trigger The visible cell row button. Wraps RAC [Button](https://react-spectrum.adobe.com/react-aria/Button.html). Also supports all RAC [Button](https://react-spectrum.adobe.com/react-aria/Button.html) props. ### CellColorPicker.Label Leading text label rendered as a `span`. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ----------- | | `children` | `ReactNode` | - | Label text | Also supports all native `span` HTML attributes. ### CellColorPicker.ValueDisplay Displays the current color as a hex value (e.g. `#FF5733`). Automatically reads from the color picker state. Also supports all native `span` HTML attributes. ### CellColorPicker.Swatch Color swatch preview. Wraps Namespace UIKit [ColorSwatch](https://namespace.com/docs/react/components/color-picker). Also supports all Namespace UIKit ColorSwatch props. ### CellColorPicker.Popover Dropdown color picker panel. Wraps Namespace UIKit `ColorPicker.Popover`. | Prop | Type | Default | Description | | ----------- | ----------- | -------------- | ----------------------------------------- | | `placement` | `Placement` | `'bottom end'` | Popover placement relative to the trigger | Also supports all Namespace UIKit ColorPicker.Popover props.
    # Cell Select **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/cell-select **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/cell-select.mdx > A compact select input styled as a settings cell, ideal for preference panels and configuration forms. ## Usage {/* DEMO cell-select-default */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSelect } from "@thenamespace/uikit"; import { ListBox } from "@thenamespace/uikit/list-box"; const themes = [ { id: "default", name: "Default" }, { id: "dark", name: "Dark" }, { id: "system", name: "System" }, ]; function ThemeItems() { return themes.map((item) => ( {item.name} )); } function ThemeSelect({ variant = "default", }: { variant?: "default" | "secondary"; }) { const [value, setValue] = useState("default"); return ( Theme ); } export const DemoDefaultExample = () => (
    ); ``` ## Anatomy Import the CellSelect component and access all parts using dot notation. ```tsx import { CellSelect } from "@thenamespace/uikit"; ; ``` ## Controlled {/* DEMO cell-select-controlled */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSelect } from "@thenamespace/uikit"; import { ListBox } from "@thenamespace/uikit/list-box"; const themes = [ { id: "default", name: "Default" }, { id: "dark", name: "Dark" }, { id: "system", name: "System" }, ]; function ThemeItems() { return themes.map((item) => ( {item.name} )); } export const DemoControlledExample = function Demo() { const [value, setValue] = useState("default"); const selected = themes.find((item) => item.id === value); return (
    Theme

    Selected: {selected?.name ?? "None"}

    ); }; ``` ## Custom Value {/* DEMO cell-select-custom-value */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { Globe02Icon, PaintBoardIcon, SmileIcon, } from "@thenamespace/uikit/icons"; import { HugeiconsIcon, type IconSvgElement } from "@thenamespace/uikit/icons"; import { CellSelect } from "@thenamespace/uikit"; import { ListBox } from "@thenamespace/uikit/list-box"; const iconSets = [ { icon: SmileIcon, id: "gravity", name: "Gravity" }, { icon: PaintBoardIcon, id: "heroicons", name: "Heroicons" }, { icon: Globe02Icon, id: "lucide", name: "Lucide" }, ]; function IconSetGlyph({ icon }: { icon: IconSvgElement }) { return ; } export const DemoCustomValueExample = function Demo() { const [value, setValue] = useState("gravity"); return (
    Icons {({ defaultChildren, isPlaceholder, state }) => { if (isPlaceholder || state.selectedItems.length === 0) return defaultChildren; const item = iconSets.find( (option) => option.id === state.selectedItems[0]?.key, ); return item ? ( {item.name} ) : ( defaultChildren ); }} {iconSets.map((item) => ( {item.name} ))}
    ); }; ``` ## Disabled {/* DEMO cell-select-disabled */} ```tsx "use client"; import { CellSelect } from "@thenamespace/uikit"; import { ListBox } from "@thenamespace/uikit/list-box"; const themes = [ { id: "default", name: "Default" }, { id: "dark", name: "Dark" }, { id: "system", name: "System" }, ]; function ThemeItems() { return themes.map((item) => ( {item.name} )); } export const DemoDisabledExample = () => (
    Theme
    ); ``` ## Font Family {/* DEMO cell-select-font-family */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSelect } from "@thenamespace/uikit"; import { ListBox } from "@thenamespace/uikit/list-box"; const fonts = [ { id: "inter", name: "Inter" }, { id: "roboto", name: "Roboto" }, { id: "system", name: "System" }, { id: "georgia", name: "Georgia" }, ]; function FontSelect({ label, variant = "default", }: { label: string; variant?: "default" | "secondary"; }) { const [value, setValue] = useState("inter"); return ( {label} {({ defaultChildren, isPlaceholder, state }) => { if (isPlaceholder || state.selectedItems.length === 0) return defaultChildren; const item = fonts.find( (option) => option.id === state.selectedItems[0]?.key, ); return item ? ( {item.name} Ag ) : ( defaultChildren ); }} {fonts.map((item) => ( {item.name} ))} ); } export const DemoFontFamilyExample = () => (
    Font Family
    ); ``` ## Settings Group {/* DEMO cell-select-settings-group */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSelect } from "@thenamespace/uikit"; import { ListBox } from "@thenamespace/uikit/list-box"; const themes = [ { id: "default", name: "Default" }, { id: "dark", name: "Dark" }, { id: "system", name: "System" }, ]; const settings = [ { label: "Theme", options: themes, value: "default" }, { label: "Language", options: [ { id: "en", name: "English" }, { id: "es", name: "Spanish" }, { id: "fr", name: "French" }, ], value: "en", }, { label: "Font size", options: [ { id: "sm", name: "Small" }, { id: "md", name: "Medium" }, { id: "lg", name: "Large" }, ], value: "md", }, ]; function SettingSelect({ setting }: { setting: (typeof settings)[number] }) { const [value, setValue] = useState(setting.value); return ( {setting.label} {setting.options.map((item) => ( {item.name} ))} ); } export const DemoSettingsGroupExample = () => (
    {settings.map((setting) => ( ))}
    ); ``` ## Variants {/* DEMO cell-select-variants */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSelect } from "@thenamespace/uikit"; import { ListBox } from "@thenamespace/uikit/list-box"; const themes = [ { id: "default", name: "Default" }, { id: "dark", name: "Dark" }, { id: "system", name: "System" }, ]; function ThemeItems() { return themes.map((item) => ( {item.name} )); } function ThemeSelect({ variant = "default", }: { variant?: "default" | "secondary"; }) { const [value, setValue] = useState("default"); return ( Theme ); } export const DemoVariantsExample = () => (
    {(["default", "secondary"] as const).map((variant) => (
    {variant}
    ))}
    ); ``` ## CSS Classes ### Base Classes - `.cell-select` - Root wrapper ### Element Classes - `.cell-select__trigger` - The visible cell row (wraps Select.Trigger) - `.cell-select__trigger--default` - Default variant trigger styling - `.cell-select__trigger--secondary` - Secondary variant trigger styling - `.cell-select__label` - Leading text label - `.cell-select__value` - Selected value display - `.cell-select__indicator` - Chevron icon - `.cell-select__popover` - Dropdown panel ## API Reference ### CellSelect The root component. Wraps Namespace UIKit [Select](https://namespace.com/docs/react/components/select) with cell-style layout. | Prop | Type | Default | Description | | --------- | -------------------------- | ----------- | -------------------- | | `variant` | `'default' \| 'secondary'` | `'default'` | Visual style variant | Also supports all [Namespace UIKit Select](https://namespace.com/docs/react/components/select) props except `variant`. ### CellSelect.Trigger The visible cell row containing label, value, and indicator. Also supports all Namespace UIKit `Select.Trigger` props. ### CellSelect.Label Leading text label rendered as a `span`. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ----------- | | `children` | `ReactNode` | - | Label text | Also supports all native `span` HTML attributes. ### CellSelect.Value Display of the currently selected value. Also supports all Namespace UIKit `Select.Value` props. ### CellSelect.Indicator Chevron icon. Defaults to a `ChevronsExpandVertical` icon when no children are provided. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | --------------------- | | `children` | `ReactNode` | - | Custom indicator icon | Also supports all Namespace UIKit `Select.Indicator` props. ### CellSelect.Popover Dropdown panel for selection options. | Prop | Type | Default | Description | | ----------- | ----------- | -------------- | ----------------------------------------- | | `placement` | `Placement` | `'bottom end'` | Popover placement relative to the trigger | Also supports all Namespace UIKit `Select.Popover` props.
    # Cell Slider **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/cell-slider **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/cell-slider.mdx > A range slider styled as a settings cell with label, value display, and step configuration. ## Usage {/* DEMO cell-slider-default */} ```tsx "use client"; import { CellSlider } from "@thenamespace/uikit"; const decimalProps = { formatOptions: { maximumFractionDigits: 2, minimumFractionDigits: 2 }, maxValue: 1, minValue: 0, step: 0.01, } as const; function SliderContents({ label }: { label: string }) { return ( {label} ); } export const DemoDefaultExample = () => (
    ); ``` ## Anatomy Import the CellSlider component and access all parts using dot notation. ```tsx import { CellSlider } from "@thenamespace/uikit"; ; ``` ## Controlled {/* DEMO cell-slider-controlled */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSlider } from "@thenamespace/uikit"; const decimalProps = { formatOptions: { maximumFractionDigits: 2, minimumFractionDigits: 2 }, maxValue: 1, minValue: 0, step: 0.01, } as const; function SliderContents({ label }: { label: string }) { return ( {label} ); } export const DemoControlledExample = function Demo() { const [value, setValue] = useState(0.5); return (

    Value: {value.toFixed(2)}

    ); }; ``` ## Disabled {/* DEMO cell-slider-disabled */} ```tsx "use client"; import { CellSlider } from "@thenamespace/uikit"; const decimalProps = { formatOptions: { maximumFractionDigits: 2, minimumFractionDigits: 2 }, maxValue: 1, minValue: 0, step: 0.01, } as const; function SliderContents({ label }: { label: string }) { return ( {label} ); } export const DemoDisabledExample = () => (
    ); ``` ## Integer Step {/* DEMO cell-slider-integer-step */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSlider } from "@thenamespace/uikit"; function SliderContents({ label }: { label: string }) { return ( {label} ); } export const DemoIntegerStepExample = function Demo() { const [value, setValue] = useState(75); return (
    ); }; ``` ## Secondary Group {/* DEMO cell-slider-secondary-group */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSlider } from "@thenamespace/uikit"; const decimalProps = { formatOptions: { maximumFractionDigits: 2, minimumFractionDigits: 2 }, maxValue: 1, minValue: 0, step: 0.01, } as const; function SliderContents({ label }: { label: string }) { return ( {label} ); } function ControlledCellSlider({ defaultValue, label, variant = "default", }: { defaultValue: number; label: string; variant?: "default" | "secondary"; }) { const [value, setValue] = useState(defaultValue); return ( ); } export const DemoSecondaryGroupExample = () => (
    ); ``` ## Settings Group {/* DEMO cell-slider-settings-group */} ```tsx // @ts-nocheck -- Complex demo data intentionally uses heterogeneous shapes. "use client"; import { useState } from "react"; import { CellSlider } from "@thenamespace/uikit"; const decimalProps = { formatOptions: { maximumFractionDigits: 2, minimumFractionDigits: 2 }, maxValue: 1, minValue: 0, step: 0.01, } as const; function SliderContents({ label }: { label: string }) { return ( {label} ); } function ControlledCellSlider({ defaultValue, label, variant = "default", }: { defaultValue: number; label: string; variant?: "default" | "secondary"; }) { const [value, setValue] = useState(defaultValue); return ( ); } export const DemoSettingsGroupExample = () => (
    Density
    Corners
    ); ``` ## Variants {/* DEMO cell-slider-variants */} ```tsx "use client"; import { CellSlider } from "@thenamespace/uikit"; const decimalProps = { formatOptions: { maximumFractionDigits: 2, minimumFractionDigits: 2 }, maxValue: 1, minValue: 0, step: 0.01, } as const; function SliderContents({ label }: { label: string }) { return ( {label} ); } export const DemoVariantsExample = () => (
    {(["default", "secondary"] as const).map((variant) => (
    {variant}
    ))}
    ); ``` ## CSS Classes ### Base Classes - `.cell-slider` - Root wrapper ### Element Classes - `.cell-slider__track` - The visible cell row (Slider.Track) - `.cell-slider__track--default` - Default variant track styling - `.cell-slider__track--secondary` - Secondary variant track styling - `.cell-slider__fill` - Accent tint showing the current value - `.cell-slider__thumb` - Transparent hit area with a `::after` pill indicator - `.cell-slider__label` - Leading text label (absolutely positioned left) - `.cell-slider__output` - Value display (absolutely positioned right) ## API Reference ### CellSlider The root component. Wraps Namespace UIKit [Slider](https://namespace.com/docs/react/components/slider) with cell-style layout. Always renders in horizontal orientation. | Prop | Type | Default | Description | | --------- | -------------------------- | ----------- | -------------------- | | `variant` | `'default' \| 'secondary'` | `'default'` | Visual style variant | Also supports all [Namespace UIKit Slider](https://namespace.com/docs/react/components/slider) props except `variant` and `orientation`. ### CellSlider.Track The visible cell row serving as the slider track. Contains fill, thumb, label, and output as children. Also supports all Namespace UIKit `Slider.Track` props. ### CellSlider.Fill Subtle accent tint showing the current slider value. Also supports all Namespace UIKit `Slider.Fill` props. ### CellSlider.Thumb Transparent hit area for drag and keyboard accessibility. The visible indicator is a `::after` pseudo-element (thin 3px pill). Also supports all Namespace UIKit `Slider.Thumb` props. ### CellSlider.Label Leading text label, absolutely positioned on the left. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ----------- | | `children` | `ReactNode` | - | Label text | Also supports all native `span` HTML attributes. ### CellSlider.Output Value display, absolutely positioned on the right. Also supports all Namespace UIKit `Slider.Output` props.
    # Cell Switch **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/cell-switch **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/cell-switch.mdx > A toggle switch styled as a settings cell with label and description, ideal for preference panels. ## Usage {/* DEMO cell-switch-default */} ```tsx "use client"; import { CellSwitch } from "@thenamespace/uikit"; function SwitchContents({ label }: { label: string }) { return ( {label} ); } export const DemoDefaultExample = () => (
    ); ``` ## Anatomy Import the CellSwitch component and access all parts using dot notation. ```tsx import { CellSwitch } from "@thenamespace/uikit"; ; ``` ## Controlled {/* DEMO cell-switch-controlled */} ```tsx "use client"; import { useState } from "react"; import { CellSwitch } from "@thenamespace/uikit"; function SwitchContents({ label }: { label: string }) { return ( {label} ); } export const DemoControlledExample = function Demo() { const [selected, setSelected] = useState(true); return (

    Animations: {selected ? "On" : "Off"}

    ); }; ``` ## Disabled {/* DEMO cell-switch-disabled */} ```tsx "use client"; import { CellSwitch } from "@thenamespace/uikit"; function SwitchContents({ label }: { label: string }) { return ( {label} ); } export const DemoDisabledExample = () => (
    ); ``` ## Feature Announcement {/* DEMO cell-switch-feature-announcement */} ```tsx "use client"; import { useState } from "react"; import { CellSwitch } from "@thenamespace/uikit"; import { Chip } from "@thenamespace/uikit/chip"; export const DemoFeatureAnnouncementExample = function Demo() { const [selected, setSelected] = useState(false); return (
    Try the new sidebar New Keep your pages, meetings, and AI within reach.
    ); }; ``` ## Secondary Group {/* DEMO cell-switch-secondary-group */} ```tsx "use client"; import { useState } from "react"; import { CellSwitch } from "@thenamespace/uikit"; function SwitchContents({ label }: { label: string }) { return ( {label} ); } function ControlledSwitch({ defaultSelected, label, variant = "default", }: { defaultSelected: boolean; label: string; variant?: "default" | "secondary"; }) { const [selected, setSelected] = useState(defaultSelected); return ( ); } export const DemoSecondaryGroupExample = () => (
    ); ``` ## Settings Group {/* DEMO cell-switch-settings-group */} ```tsx "use client"; import { useState } from "react"; import { CellSwitch } from "@thenamespace/uikit"; function SwitchContents({ label }: { label: string }) { return ( {label} ); } function ControlledSwitch({ defaultSelected, label, variant = "default", }: { defaultSelected: boolean; label: string; variant?: "default" | "secondary"; }) { const [selected, setSelected] = useState(defaultSelected); return ( ); } export const DemoSettingsGroupExample = () => (
    ); ``` ## Variants {/* DEMO cell-switch-variants */} ```tsx "use client"; import { CellSwitch } from "@thenamespace/uikit"; function SwitchContents({ label }: { label: string }) { return ( {label} ); } export const DemoVariantsExample = () => (
    {(["default", "secondary"] as const).map((variant) => (
    {variant}
    ))}
    ); ``` ## CSS Classes ### Base Classes - `.cell-switch` - Root wrapper (renders as a `label` via Namespace UIKit Switch) ### Element Classes - `.cell-switch__trigger` - The visible cell row container - `.cell-switch__trigger--default` - Default variant trigger styling - `.cell-switch__trigger--secondary` - Secondary variant trigger styling - `.cell-switch__label` - Leading text label - `.cell-switch__control` - Switch control wrapper - `.cell-switch__control--secondary` - Secondary variant control styling ### Variant Classes - `.cell-switch--secondary` - Secondary variant on the root ## API Reference ### CellSwitch The root component. Wraps Namespace UIKit [Switch](https://namespace.com/docs/react/components/switch) with cell-style layout. Clicking anywhere in the cell toggles the switch. | Prop | Type | Default | Description | | --------- | -------------------------- | ----------- | -------------------- | | `variant` | `'default' \| 'secondary'` | `'default'` | Visual style variant | Also supports all [Namespace UIKit Switch](https://namespace.com/docs/react/components/switch) props except `variant`. ### CellSwitch.Trigger The visible cell row containing the label and switch control. Renders Namespace UIKit `Switch.Content` (a `
    # Chain Of Thought **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chain-of-thought **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chain-of-thought.mdx > A collapsible reasoning timeline for assistant thinking, progress, and agent traces. ## Usage {/* DEMO chain-of-thought-default */} ```tsx "use client"; import { ChainOfThought } from "@thenamespace/uikit"; export const DemoDefaultExample = () => (
    Thought for 4 seconds Looked up Namespace UIKit chat template patterns for message layout and composer spacing. Mapped the template structure to SDK-agnostic compound components.
    ); ``` Use `ChainOfThought` to show assistant reasoning, progress, tool discovery, or agent traces without coupling to an AI SDK. ## Streaming {/* DEMO chain-of-thought-streaming */} ```tsx "use client"; import { ChainOfThought } from "@thenamespace/uikit"; export const DemoStreamingExample = () => (
    Thinking... Breaking the request into presentation-only UIKit components.
    ); ``` Set `isStreaming` to shimmer the trigger while reasoning is still in progress. ## Agent Trace {/* DEMO chain-of-thought-agent-trace */} ```tsx "use client"; import { useState, type ReactNode } from "react"; import { ChainOfThought } from "@thenamespace/uikit"; import { Icon } from "@/demos/icon"; function CollapsibleStep({ children, defaultOpen = true, icon, title, }: { children: ReactNode; defaultOpen?: boolean; icon?: ReactNode; title: string; }) { const [open, setOpen] = useState(defaultOpen); return ( setOpen((value) => !value)} > {icon ? {icon} : null} {title} } >
    {children}
    ); } function TraceSection({ children, defaultExpanded = true, isStreaming = false, title, }: { children: ReactNode; defaultExpanded?: boolean; isStreaming?: boolean; title: string; }) { return ( {title} {children} ); } function ReadFile({ children }: { children: ReactNode }) { return (
    {children}
    ); } function AgentTraceDemo({ isStreaming = false }: { isStreaming?: boolean }) { return (

    The user wants a simple login page. This is a straightforward UI task - I should create a clean login form. Let me generate some design inspiration first to ensure it looks good, then build the page.

    Generated area chart design Read app/layout.tsx Read app/globals.css

    Now let me check the existing UI components available:

    Scanning 56 files

    I have all the components I need. Let me read the button, input, card, and label components to understand their APIs:

    {[ ["button", "Read components/ui/button.tsx"], ["input", "Read components/ui/input.tsx"], ["card", "Read components/ui/card.tsx"], ["label", "Read components/ui/label.tsx"], ].map(([name, file]) => ( {file} ))}

    Now I have all the information I need. Let me create a clean, simple login page using the existing design system components:

    Updated components/login-form.tsx Updated app/layout.tsx
    ); } export const DemoAgentTraceExample = () => ; ``` Complex traces can be composed from multiple collapsible reasoning blocks and nested steps. ## Agent Trace Streaming {/* DEMO chain-of-thought-agent-trace-streaming */} ```tsx "use client"; import { useState, type ReactNode } from "react"; import { ChainOfThought } from "@thenamespace/uikit"; import { Icon } from "@/demos/icon"; function CollapsibleStep({ children, defaultOpen = true, icon, title, }: { children: ReactNode; defaultOpen?: boolean; icon?: ReactNode; title: string; }) { const [open, setOpen] = useState(defaultOpen); return ( setOpen((value) => !value)} > {icon ? {icon} : null} {title} } >
    {children}
    ); } function TraceSection({ children, defaultExpanded = true, isStreaming = false, title, }: { children: ReactNode; defaultExpanded?: boolean; isStreaming?: boolean; title: string; }) { return ( {title} {children} ); } function ReadFile({ children }: { children: ReactNode }) { return (
    {children}
    ); } function AgentTraceDemo({ isStreaming = false }: { isStreaming?: boolean }) { return (

    The user wants a simple login page. This is a straightforward UI task - I should create a clean login form. Let me generate some design inspiration first to ensure it looks good, then build the page.

    Generated area chart design Read app/layout.tsx Read app/globals.css

    Now let me check the existing UI components available:

    Scanning 56 files

    I have all the components I need. Let me read the button, input, card, and label components to understand their APIs:

    {[ ["button", "Read components/ui/button.tsx"], ["input", "Read components/ui/input.tsx"], ["card", "Read components/ui/card.tsx"], ["label", "Read components/ui/label.tsx"], ].map(([name, file]) => ( {file} ))}

    Now I have all the information I need. Let me create a clean, simple login page using the existing design system components:

    Updated components/login-form.tsx Updated app/layout.tsx
    ); } export const DemoAgentTraceStreamingExample = () => ( ); ``` Use the same trace structure with `isStreaming` when the agent is still working. ## Anatomy ```tsx import { ChainOfThought } from "@thenamespace/uikit"; Thought for 2s Read layout and globals. ; ``` ## CSS Classes - `.chain-of-thought` - Root disclosure wrapper - `.chain-of-thought__trigger` - Collapsible trigger - `.chain-of-thought__content` - Disclosure content panel - `.chain-of-thought__steps` - Vertical step timeline - `.chain-of-thought__step` - One step in the timeline - `.chain-of-thought__step-label` - Optional step label - `.chain-of-thought__step-content` - Step body content ## API Reference ### ChainOfThought Extends Namespace UIKit `Disclosure` props. | Prop | Type | Default | Description | | ----------------- | ----------- | ------- | ------------------------------------------------ | | `children` | `ReactNode` | - | Trigger and content | | `isStreaming` | `boolean` | `false` | Applies streaming shimmer styling to the trigger | | `defaultExpanded` | `boolean` | - | Open by default for uncontrolled usage | | `isExpanded` | `boolean` | - | Controlled expanded state | ### ChainOfThought.Trigger Extends Namespace UIKit `Button` props. Renders the disclosure trigger. ### ChainOfThought.Content Extends `Disclosure.Content` props. Wraps the expanded content body. ### ChainOfThought.Steps Renders the vertical timeline container. Also supports native `div` props. ### ChainOfThought.Step Renders one timeline step. Also supports native `div` props. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ---------------------------------------------- | | `label` | `ReactNode` | - | Optional label rendered above the step content | | `children` | `ReactNode` | - | Step body content |
    # Chart Tooltip **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chart-tooltip **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chart-tooltip.mdx > A composable tooltip for chart data points with customizable indicators, labels, and value formatters. ## Usage {/* DEMO chart-tooltip-default */} ```tsx "use client"; import { ChartTooltip } from "@thenamespace/uikit"; export const DemoDefaultExample = () => ( January Revenue $12,400 Expenses $8,200 ); ``` ## Anatomy Import the ChartTooltip component and access all parts using dot notation. ```tsx import { ChartTooltip } from "@thenamespace/uikit"; ; ``` ## Auto Content {/* DEMO chart-tooltip-auto-content */} ```tsx "use client"; import { ChartTooltip } from "@thenamespace/uikit"; export const DemoAutoContentExample = () => (
    ); ``` ## Chart Colors {/* DEMO chart-tooltip-chart-colors */} ```tsx "use client"; import { ChartTooltip } from "@thenamespace/uikit"; const chartColors = [ ["chart-1", "Lightest"], ["chart-2", "Light"], ["chart-3", "Accent"], ["chart-4", "Darkest"], ] as const; function ColorTooltip({ indicator = "dot" }: { indicator?: "dot" | "line" }) { return ( {indicator === "line" ? "Line Indicators" : "All Chart Colors"} {chartColors.map(([token, label]) => ( {token} {label} ))} ); } export const DemoChartColorsExample = () => (
    ); ``` ## Custom Formatters {/* DEMO chart-tooltip-custom-formatters */} ```tsx "use client"; import { ChartTooltip } from "@thenamespace/uikit"; export const DemoCustomFormattersExample = () => ( new Date(String(label)).toLocaleDateString("en-US", { day: "numeric", month: "long", year: "numeric", }) } payload={[ { name: "Portfolio", stroke: "var(--chart-3)", value: 24801.32 }, { name: "Benchmark", stroke: "var(--chart-2)", value: 21500 }, ]} valueFormatter={(value) => `$${Number(value).toLocaleString()}`} /> ); ``` ## Inactive {/* DEMO chart-tooltip-inactive */} ```tsx "use client"; import { ChartTooltip } from "@thenamespace/uikit"; export const DemoInactiveExample = () => (

    The tooltip below is inactive (active=false) — nothing should render:

    (empty — tooltip hidden)
    ); ``` ## Line Indicator {/* DEMO chart-tooltip-line-indicator */} ```tsx "use client"; import { ChartTooltip } from "@thenamespace/uikit"; export const DemoLineIndicatorExample = () => ( March 2025 {[ ["Organic", "15,200", "var(--chart-3)"], ["Paid Ads", "8,400", "var(--chart-2)"], ["Referral", "3,100", "var(--chart-1)"], ].map(([label, value, color]) => ( {label} {value} ))} ); ``` ## No Header {/* DEMO chart-tooltip-no-header */} ```tsx "use client"; import { ChartTooltip } from "@thenamespace/uikit"; export const DemoNoHeaderExample = () => ( Sales 458 ); ``` ## CSS Classes ### Element Classes - `.chart-tooltip` — Root tooltip card container. Rounded border with surface background and overlay shadow. - `.chart-tooltip__header` — Optional title row (e.g., the X-axis label). Muted 12px medium text. - `.chart-tooltip__item` — A single series entry row. Flex layout with gap. - `.chart-tooltip__indicator` — Color marker next to the series name. - `.chart-tooltip__indicator--dot` — Dot-shaped indicator. Small circle (8px). - `.chart-tooltip__indicator--line` — Line-shaped indicator. Tall narrow pill (12px × 4px). - `.chart-tooltip__label` — Series name text. Muted 12px, fills available space. - `.chart-tooltip__value` — Series data value. Semibold 12px foreground text. ## API Reference ### ChartTooltip The root tooltip container. Controls visibility and indicator style via context. | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------- | | `active` | `boolean` | `true` | Controls visibility. When `false`, the tooltip is not rendered. | | `indicator` | `"dot" \| "line"` | `"dot"` | Shape of the color indicator next to each series name. | | `children` | `ReactNode` | — | Tooltip content — typically `Header`, `Item`, `Indicator`, `Label`, and `Value` sub-components. | Also supports all native `div` HTML attributes. ### ChartTooltip.Content Auto-renders a tooltip from Recharts payload data. Pass as the `content` prop of a Recharts ``: ```tsx } /> ``` | Prop | Type | Default | Description | | ---------------- | ---------------------------------------- | ------- | ------------------------------------------------------------------- | | `active` | `boolean` | — | Provided by Recharts — whether the tooltip is active. | | `label` | `number \| string` | — | Provided by Recharts — the X-axis label for the hovered data point. | | `payload` | `RechartsPayloadEntry[]` | — | Provided by Recharts — array of series data for the hovered point. | | `hideHeader` | `boolean` | `false` | Hide the header row. | | `indicator` | `"dot" \| "line"` | `"dot"` | Shape of the color indicator. | | `labelFormatter` | `(label: number \| string) => ReactNode` | — | Custom formatter for the header label. | | `valueFormatter` | `(value: number \| string) => ReactNode` | — | Custom formatter for series values. | ### ChartTooltip.Header Optional title row rendered above the series items. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------------------------------------------- | | `children` | `ReactNode` | — | Header content (typically the X-axis label text). | Also supports all native `div` HTML attributes. ### ChartTooltip.Item A single series entry row containing an indicator, label, and value. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------------------------------------------------------------------- | | `children` | `ReactNode` | — | Row content — typically `Indicator`, `Label`, and `Value` sub-components. | Also supports all native `div` HTML attributes. ### ChartTooltip.Indicator Color marker rendered next to the series name. Shape is controlled by the root `indicator` variant. | Prop | Type | Default | Description | | ------- | -------- | ------- | --------------------------------------------- | | `color` | `string` | — | CSS color value for the indicator background. | Also supports all native `span` HTML attributes. ### ChartTooltip.Label Series name text displayed between the indicator and value. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------------------------------------------------- | | `children` | `ReactNode` | — | Label content (typically the series name or `dataKey`). | Also supports all native `span` HTML attributes. ### ChartTooltip.Value Series data value displayed at the end of each item row. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------------------------------------------------ | | `children` | `ReactNode` | — | Value content (typically the formatted numeric value). | Also supports all native `span` HTML attributes.
    # Chat Attachment **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-attachment **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-attachment.mdx > Attachment previews and composer file input helpers for AI chat surfaces. ## Usage {/* DEMO chat-attachment-default */} ```tsx "use client"; import { ChatAttachment, ChatAttachmentGroup, ChatMessage, } from "@thenamespace/uikit"; const image = "/assets/images/egg.webp"; export const DemoDefaultExample = () => ( What is in this screenshot? ); ``` Use `ChatAttachment` for compact file previews in messages or prompt composers. ## Composer {/* DEMO chat-attachment-composer */} ```tsx "use client"; import { useEffect, useRef, useState } from "react"; import { ChatAttachment, ChatAttachmentGroup, ChatAttachmentInput, PromptInput, } from "@thenamespace/uikit"; import { ArrowUp01Icon, Attachment01Icon } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; interface Attachment { id: string; mimeType: string; name: string; src?: string; } function ComposerDemo() { const [attachments, setAttachments] = useState([]); const refs = useRef([]); useEffect(() => { refs.current = attachments; }, [attachments]); useEffect( () => () => refs.current.forEach((item) => { if (item.src?.startsWith("blob:")) URL.revokeObjectURL(item.src); }), [], ); const add = (files: File[]) => setAttachments((values) => [ ...values, ...files.map((file) => ({ id: `${file.name}-${file.lastModified}-${crypto.randomUUID()}`, mimeType: file.type, name: file.name, src: file.type.startsWith("image/") ? URL.createObjectURL(file) : undefined, })), ]); return ( ( {attachments.length ? ( {attachments.map((item) => ( setAttachments((values) => values.filter((value) => value.id !== item.id), ) } /> ))} ) : null} ( )} /> )} /> ); } export const DemoComposerExample = () => ; ``` Pair `ChatAttachmentInput` with `PromptInput` to support file picker and drag-and-drop uploads. When generating local image or video previews with `URL.createObjectURL`, revoke each `blob:` URL when the attachment is removed, when the composer is cleared, and when the component unmounts. ## Grouped {/* DEMO chat-attachment-grouped */} ```tsx "use client"; import { ChatAttachment, ChatAttachmentGroup } from "@thenamespace/uikit"; const image = "/assets/images/egg.webp"; export const DemoGroupedExample = () => ( ); ``` Use `ChatAttachmentGroup` to arrange multiple attachments. ## Anatomy ```tsx import {ChatAttachment, ChatAttachmentGroup, ChatAttachmentInput, PromptInput} from "@thenamespace/uikit"; {/* composer content */}} /> ``` ## CSS Classes - `.chat-attachment` - Attachment preview tile - `.chat-attachment__preview` - Preview media wrapper - `.chat-attachment__preview-image` - Image preview - `.chat-attachment__preview-video` - Video preview - `.chat-attachment__preview-fallback` - Fallback document icon - `.chat-attachment__remove` - Remove button - `.chat-attachment-group` - Attachment group wrapper ## API Reference ### ChatAttachment | Prop | Type | Default | Description | | ----------- | ---------------------------------------------------------- | --------------- | ------------------------------------------ | | `mediaType` | `'audio' \| 'document' \| 'image' \| 'unknown' \| 'video'` | inferred | Attachment media type | | `mimeType` | `string` | - | MIME type used to infer media type | | `name` | `string` | - | Attachment file name | | `src` | `string` | - | Preview URL for image or video attachments | | `children` | `ReactNode` | default preview | Custom attachment content | ### ChatAttachment.Preview Renders image, video, or document fallback preview. Accepts `children` to replace the preview content. ### ChatAttachment.Remove Extends Namespace UIKit `CloseButton` props. Use it to remove an attachment from composer state. ### ChatAttachmentInput Provides file-picker and drag-and-drop behavior. | Prop | Type | Default | Description | | ----------------- | ------------------------- | ------- | ----------------------------------------- | | `accept` | `string` | - | Native file input accept filter | | `multiple` | `boolean` | `true` | Allow multiple files | | `disabled` | `boolean` | `false` | Disable picker and drop behavior | | `onFilesSelected` | `(files: File[]) => void` | - | Called when files are selected or dropped | ### ChatAttachmentInput.Trigger Opens the hidden file input. Use `render` to wire it to another button. ### ChatAttachmentInput.Dropzone Adds drag-and-drop file handling. Use `render` to attach drop behavior to `PromptInput.Shell`. # Chat Conversation **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-conversation **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-conversation.mdx > A stick-to-bottom conversation viewport for streaming chat messages. ## Usage {/* DEMO chat-conversation-default */} ```tsx "use client"; import { ChainOfThought, ChatConversation, ChatMessage, ChatMessageActions, Markdown, } from "@thenamespace/uikit"; interface Message { id: string; role: "assistant" | "user"; text: string; trace?: boolean; } const messages: Message[] = [ { id: "1", role: "user", text: "How does auto-scroll work in chat UIs?", }, { id: "2", role: "assistant", text: `Stick-to-bottom keeps the viewport pinned to the latest message while you stream. It also leaves the user in control: once they scroll away from the bottom, new content can arrive without snapping the viewport away from what they are reading.`, }, { id: "3", role: "user", text: "What happens when the assistant is doing multiple steps?", }, { id: "4", role: "assistant", text: "You can combine the conversation viewport with reasoning and tool UI so the whole exchange remains in one scrollable surface.", trace: true, }, ]; function MessageView({ message, showActions = false, }: { message: Message; showActions?: boolean; }) { return message.role === "user" ? ( {message.text} ) : ( {message.text} {message.trace ? ( Thought for 2s Verified the viewport remains pinned while the assistant streams. Added reasoning UI inline with the assistant message body. ) : null} {showActions ? ( ) : null} ); } function DefaultDemo() { return (
    {messages.map((message) => ( ))}
    ); } export const DemoDefaultExample = () => ; ``` Use `ChatConversation` as the scrollable message viewport for assistant and user messages. ## Full Chat {/* DEMO chat-conversation-full-chat */} ```tsx "use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { ChainOfThought, ChatConversation, ChatMessage, ChatMessageActions, Markdown, PromptInput, } from "@thenamespace/uikit"; interface Message { id: string; role: "assistant" | "user"; text: string; trace?: boolean; } function MessageView({ message, showActions = false, }: { message: Message; showActions?: boolean; }) { return message.role === "user" ? ( {message.text} ) : ( {message.text} {message.trace ? ( Thought for 2s Verified the viewport remains pinned while the assistant streams. Added reasoning UI inline with the assistant message body. ) : null} {showActions ? ( ) : null} ); } const initialFullChatMessages: Message[] = [ { id: "1", role: "user", text: "Build a settings page with profile and notifications.", }, { id: "2", role: "assistant", text: `Here is a **settings layout** outline with profile, notifications, and a danger zone section. - Profile card for avatar, display name, and email - Notification preferences grouped by channel - Danger zone with clear destructive affordances`, trace: true, }, { id: "3", role: "user", text: "Add a compact version that works well in a dashboard drawer.", }, { id: "4", role: "assistant", text: "For a drawer, I would reduce section padding, keep labels visible, and move destructive actions behind a confirmation step. The same components can stay responsive with container width constraints.", }, ]; function FullChatDemo() { const [currentMessages, setMessages] = useState(initialFullChatMessages); const [value, setValue] = useState(""); const [status, setStatus] = useState<"ready" | "streaming" | "submitted">( "ready", ); const timers = useRef([]); const clearTimers = useCallback(() => { timers.current.forEach((timer) => window.clearTimeout(timer)); timers.current = []; }, []); useEffect(() => () => clearTimers(), [clearTimers]); const stop = () => { clearTimers(); setStatus("ready"); }; const submit = () => { const text = value.trim(); if (!text || status !== "ready") return; setMessages((current) => [ ...current, { id: String(Date.now()), role: "user", text }, ]); setValue(""); setStatus("submitted"); clearTimers(); timers.current.push( window.setTimeout(() => setStatus("streaming"), 300), window.setTimeout(() => { setMessages((current) => [ ...current, { id: String(Date.now() + 1), role: "assistant", text: "This is a mocked assistant reply rendered with Namespace UIKit AI components.", }, ]); setStatus("ready"); }, 1200), ); }; return (
    {currentMessages.map((message) => ( ))}
    AI can make mistakes. Check important info.
    ); } export const DemoFullChatExample = () => ; ``` Combine `ChatConversation`, `ChatMessage`, `ChainOfThought`, and `PromptInput` for a complete chat surface. ## Scroll Button Add `ChatConversation.ScrollButton` only when your product needs an explicit jump-to-bottom control. ## Anatomy ```tsx import { ChatConversation } from "@thenamespace/uikit"; {messages} ; ``` ### With Scroll Button {/* DEMO chat-conversation-with-scroll-button */} ```tsx "use client"; import { useEffect, useRef } from "react"; import { ChainOfThought, ChatConversation, ChatMessage, ChatMessageActions, Markdown, } from "@thenamespace/uikit"; interface Message { id: string; role: "assistant" | "user"; text: string; trace?: boolean; } function MessageView({ message, showActions = false, }: { message: Message; showActions?: boolean; }) { return message.role === "user" ? ( {message.text} ) : ( {message.text} {message.trace ? ( Thought for 2s Verified the viewport remains pinned while the assistant streams. Added reasoning UI inline with the assistant message body. ) : null} {showActions ? ( ) : null} ); } const scrollMessages: Message[] = [ { id: "1", role: "user", text: "Can you summarize the release notes?", }, { id: "2", role: "assistant", text: `The release focuses on the chat surface, component polish, and small API cleanup. - Chat messages now compose with markdown, sources, and actions - Conversation viewports keep streaming content readable - AI components share the same spacing and typography rhythm`, }, { id: "3", role: "user", text: "What changed for long conversations?", }, { id: "4", role: "assistant", text: "Long threads keep their message column centered while the viewport owns scrolling. The optional jump button can be added when an app wants an explicit return-to-bottom control.", }, { id: "5", role: "user", text: "Show me the button treatment.", }, { id: "6", role: "assistant", text: "Scroll away from the latest response and the button appears over the lower edge of the conversation. Pressing it returns the viewport to the newest message.", }, { id: "7", role: "assistant", text: "The default conversation examples omit this control so teams can choose whether the extra affordance belongs in their product.", }, ]; function WithScrollButtonDemo() { const conversationRef = useRef(null); useEffect(() => { const frame = window.requestAnimationFrame(() => { const conversation = conversationRef.current; conversation?.scrollTo({ behavior: "auto", top: Math.max( 0, conversation.scrollHeight - conversation.clientHeight - 160, ), }); }); return () => window.cancelAnimationFrame(frame); }, []); return (
    {scrollMessages.map((message) => ( ))}
    ); } export const DemoWithScrollButtonExample = () => ; ``` ## CSS Classes - `.chat-conversation` - Scrollable root viewport - `.chat-conversation__content` - Message column - `.chat-conversation__scroll-button` - Jump-to-bottom button - `.chat-conversation__scroll-button-container` - Jump-to-bottom button positioner - `.chat-conversation__scroll-anchor` - Bottom scroll anchor ## API Reference ### ChatConversation Root scroll viewport. Supports native `div` props. ### ChatConversation.Content Centers and stacks conversation content. Supports native `div` props. ### ChatConversation.ScrollButton Optional button that appears when the viewport is away from the bottom. Extends Namespace UIKit `Button` props. ### ChatConversation.ScrollAnchor Bottom anchor used by the stick-to-bottom behavior. Supports native `div` props.
    # Chat List View **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-list-view **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-list-view.mdx > Thread list rows for chat sidebars and conversation pickers. ## Usage {/* DEMO chat-list-view-default */} ```tsx "use client"; import { Comment01Icon } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { ChatListView } from "@thenamespace/uikit"; const chats = [ { id: "1", preview: "Can you help me draft a launch checklist?", title: "Product launch planning", updatedAt: "2h ago", }, { id: "2", preview: "Summarize the customer feedback from last week.", title: "Customer feedback review", updatedAt: "Yesterday", }, { id: "3", preview: "Rewrite this paragraph to sound more concise.", title: "Copy editing help", updatedAt: "Mon", }, ]; function DefaultDemo() { return (
    {(chat) => ( {chat.title} {chat.preview} {chat.updatedAt} )}
    ); } export const DemoDefaultExample = () => ; ``` Use `ChatListView` to render recent conversations or saved threads. ## Compact {/* DEMO chat-list-view-compact */} ```tsx "use client"; import { Comment01Icon } from "@thenamespace/uikit/icons"; import { HugeiconsIcon } from "@thenamespace/uikit/icons"; import { ChatListView } from "@thenamespace/uikit"; const chats = [ { id: "1", preview: "Can you help me draft a launch checklist?", title: "Product launch planning", updatedAt: "2h ago", }, { id: "2", preview: "Summarize the customer feedback from last week.", title: "Customer feedback review", updatedAt: "Yesterday", }, { id: "3", preview: "Rewrite this paragraph to sound more concise.", title: "Copy editing help", updatedAt: "Mon", }, ]; function CompactDemo() { return (
    {(chat) => ( {chat.title} {chat.preview} {chat.updatedAt} )}
    ); } export const DemoCompactExample = () => ; ``` Use the compact variant for dense sidebars. ## Anatomy ```tsx import { ChatListView } from "@thenamespace/uikit"; Project planning Last assistant reply preview ; ``` ## CSS Classes - `.chat-list-view` - Root list - `.chat-list-view__item` - Thread row - `.chat-list-view__icon` - Leading icon/avatar - `.chat-list-view__item-content` - Row text content - `.chat-list-view__title` - Thread title - `.chat-list-view__preview` - Thread preview - `.chat-list-view__meta` - Optional metadata ## API Reference ### ChatListView Root list container. Supports native `div` props. ### ChatListView\.Item Thread row. Supports native button/link composition depending on usage. ### ChatListView\.Icon Leading icon slot. ### ChatListView\.ItemContent Text content wrapper. ### ChatListView\.Title Thread title slot. ### ChatListView\.Preview Thread description or last-message preview slot. ### ChatListView\.Meta Optional metadata slot, such as date or unread counts.
    # Chat Loader **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-loader **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-loader.mdx > Loading skeletons and typing placeholders for assistant responses. ## Usage {/* DEMO chat-loader-default */} ```tsx "use client"; import { ChatLoader } from "@thenamespace/uikit"; export const DemoDefaultExample = () => (
    Dots
    Pulse
    Spinner
    ); ``` Use `ChatLoader` while an assistant response or thread is loading. ## Anatomy ```tsx import {ChatLoader} from "@thenamespace/uikit"; ``` ## CSS Classes - `.chat-loader` - Root loading layout - `.chat-loader__avatar` - Avatar placeholder - `.chat-loader__content` - Loading line group - `.chat-loader__line` - Individual loading line ## API Reference ### ChatLoader.Dots Animated dot loader. Supports native `div` props. ### ChatLoader.Pulse Pulse loader. Supports native `div` props. ### ChatLoader.Spinner Spinner loader. Supports native `div` props. ### ChatLoader.Skeleton Chat-message-shaped loading skeleton. Supports native `div` props. ### ChatLoader.SkeletonAvatar, SkeletonBlock, SkeletonLine Composable skeleton primitives used by `ChatLoader.Skeleton`.
    # Chat Message Actions **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-message-actions **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-message-actions.mdx > Inline action buttons for assistant and user messages. ## Usage {/* DEMO chat-message-actions-default */} ```tsx "use client"; import { ChatMessage, ChatMessageActions } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoDefaultExample = () => ( Assistant responses can expose quick actions beneath the message body. ); ``` Use `ChatMessageActions` for copy, retry, rating, share, or custom message actions. ## Minimal {/* DEMO chat-message-actions-minimal */} ```tsx "use client"; import { ChatMessage, ChatMessageActions } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoMinimalExample = () => ( Minimal action set for compact layouts. ); ``` Use a smaller action set when only one or two actions are needed. ## Custom Icons {/* DEMO chat-message-actions-custom-icons */} ```tsx "use client"; import { ChatMessage, ChatMessageActions } from "@thenamespace/uikit"; import { Copy01Icon, HugeiconsIcon, ThumbsUpIcon } from "@thenamespace/uikit/icons"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoCustomIconsExample = () => ( Swap preset icons via the Icon subcomponents. ); ``` Actions are composable, so you can bring your own icon set. ## Anatomy ```tsx import { ChatMessageActions } from "@thenamespace/uikit"; ... ... ; ``` ## CSS Classes - `.chat-message-actions` - Root action row - `.chat-message-actions__action` - Individual icon button ## API Reference ### ChatMessageActions Root action group. Supports native `div` props. ### ChatMessageActions.Action Individual action button. Extends Namespace UIKit `Button` props. # Chat Message **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-message **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-message.mdx > Composable user and assistant message layouts for AI chat. ## Usage {/* DEMO chat-message-default */} ```tsx "use client"; import { ChatMessage } from "@thenamespace/uikit"; import { Icon } from "@/demos/icon"; const Wrapper = ({ children }: { children: React.ReactNode }) => (
    {children}
    ); export const DemoDefaultExample = () => ( Can you explain how compound components help AI chat UIs stay SDK-agnostic? Compound components let you compose message layout explicitly while keeping state in your app layer. ); ``` Use `ChatMessage` to compose assistant and user turns with avatars, bubbles, media, markdown, actions, and attachments. ## With Markdown {/* DEMO chat-message-with-markdown */} ```tsx "use client"; import { ChatMessage, Markdown } from "@thenamespace/uikit"; const Wrapper = ({ children }: { children: React.ReactNode }) => (
    {children}
    ); const markdown = 'Here is a concise answer with **markdown** support:\n\n```ts\nexport type ChatStatus = "ready" | "streaming";\n```\n\nThe UI stays presentation-only — your SDK owns the message array.'; export const DemoWithMarkdownExample = () => ( Show me markdown inside assistant messages. {markdown} ); ``` Render assistant responses with rich markdown content. ## Loading {/* DEMO chat-message-loading */} ```tsx "use client"; import { ChatLoader, ChatMessage, TextShimmer } from "@thenamespace/uikit"; const Wrapper = ({ children }: { children: React.ReactNode }) => (
    {children}
    ); export const DemoLoadingExample = () => ( What is the weather in San Francisco? Thinking... ); ``` Use the loading demo for pending assistant turns. ## Anatomy ```tsx import {ChatMessage, Markdown} from "@thenamespace/uikit"; {response} Hello ``` ## CSS Classes - `.chat-message--assistant` - Assistant message row - `.chat-message--user` - User message wrapper - `.chat-message__avatar` - Avatar slot - `.chat-message__body` - Assistant body column - `.chat-message__bubble` - User bubble - `.chat-message__content` - Message content - `.chat-message__actions` - Action row ## API Reference ### ChatMessage.Assistant Assistant message root. Supports native `div` props. ### ChatMessage.User User message root. Supports native `div` props. ### ChatMessage.Avatar Avatar slot for assistant messages. Extends Namespace UIKit `Avatar` props. ### ChatMessage.Body Assistant content column. ### ChatMessage.Bubble User message bubble. ### ChatMessage.Content Message text/content slot. ### ChatMessage.Actions Container for message actions.
    # Chat Source **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-source **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-source.mdx > Citation chips and grouped source lists for AI responses. ## Usage {/* DEMO chat-source-default */} ```tsx "use client"; import { ChatMessage, ChatSource } from "@thenamespace/uikit"; const favicon = (url: string) => `/assets/favicons/${new URL(url).hostname.replaceAll(".", "-")}.png`; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoDefaultExample = () => ( Here is an answer backed by a single web source. ); ``` Use `ChatSource` to cite a URL or uploaded document inline with an assistant answer. ## Document {/* DEMO chat-source-document */} ```tsx "use client"; import { ChatMessage, ChatSource } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoDocumentExample = () => ( Referenced an uploaded document. ); ``` Set `sourceType="document"` for uploaded or local files. ## Grouped {/* DEMO chat-source-grouped */} ```tsx "use client"; import { ChatMessage, ChatSource, ChatSources } from "@thenamespace/uikit"; const favicon = (url: string) => `/assets/favicons/${new URL(url).hostname.replaceAll(".", "-")}.png`; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoGroupedExample = () => ( Answer synthesized from multiple sources. 3 sources ); ``` Use `ChatSources` to group multiple citations behind a collapsible trigger. ## Stacked Favicons {/* DEMO chat-source-stacked-favicons */} ```tsx "use client"; import { ChatMessage, ChatSource, ChatSources } from "@thenamespace/uikit"; const favicon = (url: string) => `/assets/favicons/${new URL(url).hostname.replaceAll(".", "-")}.png`; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoStackedFaviconsExample = () => { const sources = [ { href: "https://www.reuters.com", label: "Reuters" }, { href: "https://nypost.com", label: "New York Post" }, { href: "https://www.foxsports.com", label: "Fox Sports" }, ]; return ( Answer synthesized from multiple sources. {sources.map((source) => ( ))} Sources {sources.map((source) => ( ))} ); }; ``` For a compact source button, render stacked favicon avatars in the `ChatSources.Trigger`. ## Composable {/* DEMO chat-source-composable */} ```tsx "use client"; import { ChatMessage, ChatSource } from "@thenamespace/uikit"; const favicon = (url: string) => `/assets/favicons/${new URL(url).hostname.replaceAll(".", "-")}.png`; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoComposableExample = () => ( React's documentation has a clear explanation of component composition and state-driven rendering. The source chip below uses custom trigger content with a fetched favicon. React docs
    react.dev

    Official React documentation for learning modern React patterns.

    ); ``` Use compound parts when you need custom trigger content. ## Anatomy ```tsx import {ChatSource, ChatSources} from "@thenamespace/uikit"; 3 sources ``` ## CSS Classes - `.chat-source` - Source root - `.chat-source__trigger` - Trigger wrapper - `.chat-source__trigger-link` - Link or document pill - `.chat-source__icon` - Favicon image or custom icon - `.chat-source__icon-fallback` - Initial fallback for URL sources - `.chat-source__document-icon` - Document source icon - `.chat-source__preview` - Hover preview popover - `.chat-sources` - Grouped source disclosure - `.chat-sources__trigger` - Group trigger - `.chat-sources__list` - Expanded source list ## API Reference ### ChatSource | Prop | Type | Default | Description | | --------------- | --------------------- | ----------------------- | -------------------------------------------------------------------------- | | `href` | `string` | - | URL source link | | `title` | `string` | domain | Display title | | `description` | `string` | - | Enables and populates the hover preview | | `enablePreview` | `boolean` | auto | Force-enable custom preview content or disable the automatic hover preview | | `faviconUrl` | `string` | - | Favicon image shown in trigger and preview | | `sourceType` | `'url' \| 'document'` | inferred | Source type | | `children` | `ReactNode` | default trigger/preview | Custom source composition | ### ChatSource.Trigger Renders the source pill trigger. Extends native anchor props for URL sources. ### ChatSource.Icon Renders a custom icon or favicon. | Prop | Type | Default | Description | | ------------ | ----------- | ---------------- | ------------------- | | `faviconUrl` | `string` | - | Favicon image URL | | `children` | `ReactNode` | fallback initial | Custom icon element | ### ChatSource.Title Renders the source title. ### ChatSource.Preview Renders the hover preview content for URL sources with title or description. If you provide a custom preview without root `title` or `description`, set `enablePreview` on `ChatSource` so the required hover-card wrapper is mounted. ### ChatSources Grouped source disclosure. Extends Namespace UIKit `Disclosure` props. ### ChatSources.Trigger Renders the grouped source trigger. ### ChatSources.Content Renders the expanded grouped source content. ### ChatSources.List Renders the flex-wrapped source list.
    # Chat Tool **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/chat-tool **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/chat-tool.mdx > Collapsible tool-call cards for inputs, outputs, errors, approvals, and grouped tool activity. ## Usage {/* DEMO chat-tool-default */} ```tsx "use client"; import { ChatMessage, ChatTool } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoDefaultExample = () => ( Completed tool call with JSON args and result. ); ``` Use `ChatTool` to show tool calls emitted by an agent or assistant. ## Streaming {/* DEMO chat-tool-streaming */} ```tsx "use client"; import { ChatMessage, ChatTool } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoStreamingExample = () => ( ); ``` Use streaming state while tool input is still being generated. ## Error Use error state for failed tool calls. ## Approval {/* DEMO chat-tool-approval */} ```tsx "use client"; import { ChatMessage, ChatTool } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoApprovalExample = () => ( {}} onReject={() => {}} /> ); ``` Use approval state when a tool requires user confirmation. ## Grouped {/* DEMO chat-tool-grouped */} ```tsx "use client"; import { ChatMessage, ChatTool, ChatToolGroup } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoGroupedExample = () => ( 2 tool calls {["searchDocs", "fetchPage"].map((toolName) => ( ))} ); ``` Use `ChatToolGroup` to group consecutive tool calls. ## Composable {/* DEMO chat-tool-composable */} ```tsx "use client"; import { ChatMessage, ChatTool } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoComposableExample = () => ( Used tool: getWeather ); ``` Tool cards expose slots for custom trigger and content layouts. ## Anatomy ```tsx import {ChatTool, ChatToolGroup} from "@thenamespace/uikit"; Used tool: searchDocs ``` ### Error State {/* DEMO chat-tool-error-state */} ```tsx "use client"; import { ChatMessage, ChatTool } from "@thenamespace/uikit"; const Assistant = ({ children }: { children: React.ReactNode }) => ( {children} ); export const DemoErrorStateExample = () => ( ); ``` ## CSS Classes - `.chat-tool` - Tool card root - `.chat-tool__trigger` - Collapsible trigger - `.chat-tool__content` - Disclosure content panel - `.chat-tool__args` - Tool input content - `.chat-tool__result` - Tool output content - `.chat-tool__error` - Tool error content - `.chat-tool-group` - Grouped tool root ## API Reference ### ChatTool | Prop | Type | Default | Description | | ----------------- | ----------------------------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------- | | `toolName` | `string` | - | Tool display name | | `state` | `'input-streaming' \| 'input-available' \| 'output-available' \| 'output-error' \| 'requires-action'` | - | Tool state | | `triggerPrefix` | `ReactNode` | - | Optional label rendered before `toolName` in preset mode | | `input` | `unknown` | - | Tool input rendered as JSON in preset mode | | `output` | `unknown` | - | Tool output rendered as JSON in preset mode | | `argsText` | `string` | - | Preformatted tool input text, useful while streaming partial JSON | | `errorText` | `string` | - | Error details rendered for `output-error` state | | `onApprove` | `() => void` | - | Called by the preset approval action in `requires-action` state | | `onReject` | `() => void` | - | Called by the preset rejection action in `requires-action` state | | `defaultExpanded` | `boolean` | - | Open by default | | `isExpanded` | `boolean` | - | Controlled expanded state | ### ChatTool.Trigger Renders the collapsible tool trigger. ### ChatTool.Content Renders the expanded tool body. ### ChatTool.Args, Result, Error, Approval Semantic slots for tool input, output, error details, and approval controls. ### ChatToolGroup Groups multiple `ChatTool` items. Extends Namespace UIKit `Disclosure` props. # Checkbox Button Group **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/checkbox-button-group **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/checkbox-button-group.mdx > A multi-selection button group with card-style checkboxes, icons, and custom indicators. ## Usage {/* DEMO checkbox-button-group-default */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; const features = [ { description: "Real-time threat detection and prevention", value: "security", }, { description: "Cloud-based storage with automatic backups", value: "storage", }, { description: "Usage reports and performance dashboards", value: "analytics", }, ]; function FeatureContent({ description, value, }: { description: string; value: string; }) { return ( {description} ); } export const DemoDefaultExample = () => ( Choose all that apply to your project {features.map((feature) => ( ))} ); ``` ## Anatomy Import the CheckboxButtonGroup component and access all parts using dot notation. ```tsx import { CheckboxButtonGroup } from "@thenamespace/uikit"; ; ``` ## Controlled {/* DEMO checkbox-button-group-controlled */} ```tsx "use client"; import { useState } from "react"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { NumberValue } from "@thenamespace/uikit/number-value"; const addons = [ { description: "Automated daily backups", price: 5, title: "Backups", value: "backups", }, { description: "24/7 monitoring and alerts", price: 12, title: "Monitoring", value: "monitoring", }, { description: "Priority email and chat support", price: 8, title: "Support", value: "support", }, ]; function AddonContent({ addon }: { addon: (typeof addons)[number] }) { return (
    {addon.description}
    /mo
    ); } export const DemoControlledExample = function Demo() { const [value, setValue] = useState(["backups"]); return (
    Select the add-ons you need {addons.map((addon) => ( ))}

    Selected:{" "} {value.join(", ") || "None"}

    ); }; ``` ## Custom Indicator {/* DEMO checkbox-button-group-custom-indicator */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { NumberValue } from "@thenamespace/uikit/number-value"; import { Icon } from "@/demos/icon"; export const DemoCustomIndicatorExample = () => ( {[ ["product-updates", "Weekly product updates and tips", 4200], ["security-alerts", "Security alerts and maintenance notices", 8100], ["marketing", "Promotions, deals, and special offers", 2300], ].map(([value, description, subscribers]) => ( {description} {" "} subscribers ))} ); ``` ## Disabled Group {/* DEMO checkbox-button-group-disabled-group */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; const features = [ { description: "Real-time threat detection and prevention", value: "security", }, { description: "Cloud-based storage with automatic backups", value: "storage", }, { description: "Usage reports and performance dashboards", value: "analytics", }, ]; function FeatureContent({ description, value, }: { description: string; value: string; }) { return ( {description} ); } export const DemoDisabledGroupExample = () => ( Feature selection is temporarily unavailable. {features.map((feature) => ( ))} ); ``` ## Grid Layout {/* DEMO checkbox-button-group-grid-layout */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { NumberValue } from "@thenamespace/uikit/number-value"; const addons = [ { description: "Automated daily backups", price: 5, title: "Backups", value: "backups", }, { description: "24/7 monitoring and alerts", price: 12, title: "Monitoring", value: "monitoring", }, { description: "Priority email and chat support", price: 8, title: "Support", value: "support", }, ]; function AddonContent({ addon }: { addon: (typeof addons)[number] }) { return (
    {addon.description}
    /mo
    ); } export const DemoGridLayoutExample = () => ( {addons.map((addon) => ( ))} ); ``` ## Icon Cards {/* DEMO checkbox-button-group-icon-cards */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Chip } from "@thenamespace/uikit/chip"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { Icon } from "@/demos/icon"; const securityFeatures = [ { badge: "Recommended", description: "Two-factor authentication for all user accounts.", icon: "solar:lock-password-outline", title: "2FA", value: "2fa", }, { description: "Encrypt data at rest and in transit.", icon: "solar:shield-keyhole-outline", title: "Encryption", value: "encryption", }, { description: "Automated daily backups to secure cloud storage.", icon: "solar:cloud-outline", title: "Cloud Backup", value: "cloud-backup", }, { description: "Real-time alerts for incidents and breaches.", icon: "solar:notification-unread-outline", title: "Alert System", value: "alerts", }, ]; export const DemoIconCardsExample = () => ( {securityFeatures.map((feature) => (
    {feature.badge ? ( {feature.badge} ) : null}
    {feature.description}
    ))}
    ); ``` ## No Indicator {/* DEMO checkbox-button-group-no-indicator */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; const features = [ { description: "Real-time threat detection and prevention", value: "security", }, { description: "Cloud-based storage with automatic backups", value: "storage", }, { description: "Usage reports and performance dashboards", value: "analytics", }, ]; function FeatureContent({ description, value, }: { description: string; value: string; }) { return ( {description} ); } export const DemoNoIndicatorExample = () => ( {features.map((feature) => ( ))} ); ``` ## Render Prop Children {/* DEMO checkbox-button-group-render-prop-children */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { NumberValue } from "@thenamespace/uikit/number-value"; const addons = [ { description: "Automated daily backups", price: 5, title: "Backups", value: "backups", }, { description: "24/7 monitoring and alerts", price: 12, title: "Monitoring", value: "monitoring", }, { description: "Priority email and chat support", price: 8, title: "Support", value: "support", }, ]; export const DemoRenderPropChildrenExample = () => ( {addons.map((addon) => ( {() => ( <> {addon.description} /mo )} ))} ); ``` ## Subscription Plans {/* DEMO checkbox-button-group-subscription-plans */} ```tsx "use client"; import type { CSSProperties } from "react"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Button } from "@thenamespace/uikit/button"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { Link } from "@thenamespace/uikit/link"; import { NumberValue } from "@thenamespace/uikit/number-value"; import { Icon } from "@/demos/icon"; const plans = [ [ "gold", "Gold", 0.4, 12, "Full suite of saving, investing, and learning tools for you and your family.", ], ["silver", "Silver", 0.2, 6, "Level up your saving and investing skills with even more tools."], ["bronze", "Bronze", 0.1, 3, "Investing tools to get you started on your financial journey."], ] as const; export const DemoSubscriptionPlansExample = () => (

    Choose a subscription

    Pick a plan. Try a month on us!

    {plans.map(([value, title, daily, monthly, description]) => ( {({ isSelected }) => ( <> {description}

    /day {" "} ( {" "} billed monthly)

    )}
    ))}

    *APY is variable and subject to change at our discretion, without prior notice.

    Compare plans
    ); ``` ## With Icons {/* DEMO checkbox-button-group-with-icons */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { Icon } from "@/demos/icon"; const permissions = [ [ "content", "Content Management", "Create, edit, and delete content", "solar:cloud-outline", ], [ "users", "User Administration", "Manage team members and roles", "solar:shield-keyhole-outline", ], [ "analytics", "Analytics Access", "View and export reports", "solar:database-outline", ], [ "settings", "Settings", "Configure system preferences", "solar:lock-outline", ], ]; export const DemoWithIconsExample = () => ( {permissions.map(([value, title, description, icon]) => (
    {description}
    ))}
    ); ``` ## With Ripple {/* DEMO checkbox-button-group-with-ripple */} ```tsx "use client"; import { CheckboxButtonGroup } from "@thenamespace/uikit"; import { Description } from "@thenamespace/uikit/description"; import { Label } from "@thenamespace/uikit/label"; import { PressableFeedback } from "@thenamespace/uikit/pressable-feedback"; export const DemoWithRippleExample = () => ( {["GitHub", "Slack", "Linear"].map((title) => ( Connect your {title} integration ))} ); ``` ## CSS Classes ### Base Classes - `.checkbox-button-group` - Base CheckboxGroup container with flex layout ### Layout Classes - `.checkbox-button-group--grid` - Grid layout mode ### Element Classes - `.checkbox-button-group__item` - Card-like checkbox button with border and selection ring - `.checkbox-button-group__indicator` - Positioned top-right indicator (checkbox control or custom icon) - `.checkbox-button-group__item-content` - Text/content area wrapping Checkbox.Content - `.checkbox-button-group__item-icon` - Leading icon container ### Interactive States - **Selected**: `[data-selected="true"]` on `.checkbox-button-group__item` (accent ring) - **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]` on `.checkbox-button-group__item` (focus ring) - **Disabled**: `:disabled` or `[aria-disabled="true"]` on `.checkbox-button-group__item` (reduced opacity) ### CSS Variables - `--checkbox-button-group-item-radius` - Border radius of items (default: `var(--radius-2xl)`) ## API Reference ### CheckboxButtonGroup The root component. Wraps Namespace UIKit [CheckboxGroup](https://namespace.com/docs/react/components/checkbox-group) with card-style layout. | Prop | Type | Default | Description | | -------- | ------------------ | -------- | --------------------- | | `layout` | `'flex' \| 'grid'` | `'flex'` | Layout mode for items | Also supports all [Namespace UIKit CheckboxGroup](https://namespace.com/docs/react/components/checkbox-group) props. ### CheckboxButtonGroup.Item A selectable card wrapping Namespace UIKit Checkbox. Supports render prop children for accessing selection state. Also supports all [Namespace UIKit Checkbox](https://namespace.com/docs/react/components/checkbox) props. ### CheckboxButtonGroup.Indicator Selection indicator positioned at the top-right of the item. - **No children**: renders the default Namespace UIKit checkbox (Control + Indicator) - **With children**: renders a custom icon that appears only when selected | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------------------------------------- | | `children` | `ReactNode` | - | Custom indicator icon (shown when selected) | Also supports all native `span` HTML attributes. ### CheckboxButtonGroup.ItemContent Content area for title and description text. Wraps Namespace UIKit `Checkbox.Content`. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ---------------- | | `children` | `ReactNode` | - | Content elements | Also supports all native `div` HTML attributes. ### CheckboxButtonGroup.ItemIcon Leading icon container. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------ | | `children` | `ReactNode` | - | Icon element | Also supports all native `div` HTML attributes.
    # CheckboxGroup **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/checkbox-group **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/checkbox-group.mdx > A checkbox group component for managing multiple checkbox selections ## Import ```tsx import { CheckboxGroup, Checkbox, Label, Description, } from "@thenamespace/uikit"; ``` ### Usage ```tsx import {Checkbox, CheckboxGroup, Description, Label} from "@thenamespace/uikit"; export function Basic() { return ( Choose all that apply Coding Love building software Design Enjoy creating beautiful interfaces Writing Passionate about content creation ); } ``` ### Anatomy Import the CheckboxGroup component and access all parts using dot notation. ```tsx import { CheckboxGroup, Checkbox, Label, Description, FieldError, } from "@thenamespace/uikit"; export default () => ( ); ``` ### In Surface When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds. ```tsx import {Checkbox, CheckboxGroup, Description, Label, Surface} from "@thenamespace/uikit"; export function OnSurface() { return ( Choose all that apply Coding Love building software Design Enjoy creating beautiful interfaces Writing Passionate about content creation ); } ``` ### With Custom Indicator ```tsx "use client"; import {Checkbox, CheckboxGroup, Description, Label} from "@thenamespace/uikit"; export function WithCustomIndicator() { return ( Select the features you want {({isSelected}) => isSelected ? ( ) : null } Email notifications Receive updates via email {({isSelected}) => isSelected ? ( ) : null } Newsletter Get weekly newsletters ); } ``` ### Indeterminate ```tsx "use client"; import {Checkbox, CheckboxGroup} from "@thenamespace/uikit"; import {useState} from "react"; export function Indeterminate() { const [selected, setSelected] = useState(["coding"]); const allOptions = ["coding", "design", "writing"]; return (
    0 && selected.length < allOptions.length} isSelected={selected.length === allOptions.length} name="select-all" onChange={(isSelected: boolean) => { setSelected(isSelected ? allOptions : []); }} > Select all
    Coding Design Writing
    ); } ``` ### Controlled ```tsx "use client"; import {Checkbox, CheckboxGroup, Label} from "@thenamespace/uikit"; import {useState} from "react"; export function Controlled() { const [selected, setSelected] = useState(["coding", "design"]); return ( Coding Design Writing ); } ``` ### Validation ```tsx "use client"; import {Button, Checkbox, CheckboxGroup, FieldError, Form, Label} from "@thenamespace/uikit"; export function Validation() { return (
    { e.preventDefault(); const formData = new FormData(e.currentTarget); const values = formData.getAll("preferences"); alert(`Selected preferences: ${values.join(", ")}`); }} > Email notifications SMS notifications Push notifications Please select at least one notification method.
    ); } ``` ### Disabled ```tsx import {Checkbox, CheckboxGroup, Description, Label} from "@thenamespace/uikit"; export function Disabled() { return ( Feature selection is temporarily disabled Feature 1 This feature is coming soon Feature 2 This feature is coming soon ); } ``` ### Features and Add-ons Example ```tsx import { Checkbox, CheckboxGroup, Description, Label, } from "@thenamespace/uikit"; import { Comment01Icon, HugeiconsIcon, Mail01Icon, Notification01Icon, } from "@thenamespace/uikit/icons"; import clsx from "clsx"; export function FeaturesAndAddOns() { const addOns = [ { description: "Receive updates via email", icon: Mail01Icon, title: "Email Notifications", value: "email", }, { description: "Get instant SMS notifications", icon: Comment01Icon, title: "SMS Alerts", value: "sms", }, { description: "Browser and mobile push alerts", icon: Notification01Icon, title: "Push Notifications", value: "push", }, ]; return (
    Choose how you want to receive updates
    {addOns.map((addon) => (
    {addon.title} {addon.description}
    ))}
    ); } ``` ### Custom Render Function ```tsx "use client"; import {Checkbox, CheckboxGroup, Description, Label} from "@thenamespace/uikit"; export function CustomRenderFunction() { return (
    }> Choose all that apply Coding Love building software Design Enjoy creating beautiful interfaces Writing Passionate about content creation ); } ``` ## Styling ### Passing Tailwind CSS classes You can customize the CheckboxGroup component: ```tsx import { CheckboxGroup, Checkbox, Label } from "@thenamespace/uikit"; function CustomCheckboxGroup() { return ( Option 1 ); } ``` ### Customizing the component classes To customize the CheckboxGroup component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .checkbox-group { @apply flex flex-col gap-2; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The CheckboxGroup component uses these CSS classes: - `.checkbox-group` - Base checkbox group container ## API Reference ### CheckboxGroup Props Inherits from [React Aria CheckboxGroup](https://react-spectrum.adobe.com/react-aria/CheckboxGroup.html). | Prop | Type | Default | Description | | -------------- | -------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------- | | `value` | `string[]` | - | The current selected values (controlled) | | `defaultValue` | `string[]` | - | The default selected values (uncontrolled) | | `onChange` | `(value: string[]) => void` | - | Handler called when the selected values change | | `isDisabled` | `boolean` | `false` | Whether the checkbox group is disabled | | `isRequired` | `boolean` | `false` | Whether the checkbox group is required | | `isReadOnly` | `boolean` | `false` | Whether the checkbox group is read only | | `isInvalid` | `boolean` | `false` | Whether the checkbox group is in an invalid state | | `name` | `string` | - | The name of the checkbox group, used when submitting an HTML form | | `children` | `React.ReactNode \| (values: CheckboxGroupRenderProps) => React.ReactNode` | - | Checkbox group content or render prop | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### CheckboxGroupRenderProps When using the render prop pattern, these values are provided: | Prop | Type | Description | | ------------ | ---------- | ------------------------------------------------- | | `value` | `string[]` | The currently selected values | | `isDisabled` | `boolean` | Whether the checkbox group is disabled | | `isReadOnly` | `boolean` | Whether the checkbox group is read only | | `isInvalid` | `boolean` | Whether the checkbox group is in an invalid state | | `isRequired` | `boolean` | Whether the checkbox group is required | # Checkbox **Category**: components **URL**: https://namespace-uikit.vercel.app/docs/components/checkbox **Source**: https://github.com/thenamespace/uikit/blob/main/apps/docs/content/docs/components/checkbox.mdx > Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected. ## Import ```tsx import { Checkbox } from "@thenamespace/uikit"; ``` ### Usage ```tsx import {Checkbox} from "@thenamespace/uikit"; export function Basic() { return ( Accept terms and conditions ); } ``` ### Anatomy Import the Checkbox component and access all parts using dot notation. ```tsx import { Checkbox, Description, FieldError } from "@thenamespace/uikit"; export default () => ( Label {/* plain text — the clickable label + accessible name */} {/* Optional — field-level help text */} {/* Optional — validation message */} ); ``` ### Disabled ```tsx import {Checkbox, Description} from "@thenamespace/uikit"; export function Disabled() { return ( Premium Feature This feature is coming soon ); } ``` ### Default Selected ```tsx import {Checkbox} from "@thenamespace/uikit"; export function DefaultSelected() { return ( Enable email notifications ); } ``` ### Controlled ```tsx "use client"; import {Checkbox} from "@thenamespace/uikit"; import {useState} from "react"; export function Controlled() { const [isSelected, setIsSelected] = useState(true); return (
    Email notifications

    Status: {isSelected ? "Enabled" : "Disabled"}

    ); } ``` ### Indeterminate ```tsx "use client"; import {Checkbox, Description} from "@thenamespace/uikit"; import {useState} from "react"; export function Indeterminate() { const [isIndeterminate, setIsIndeterminate] = useState(true); const [isSelected, setIsSelected] = useState(false); return ( { setIsSelected(selected); setIsIndeterminate(false); }} > Select all Shows indeterminate state (dash icon) ); } ``` ### External Label ```tsx import {Checkbox, Label} from "@thenamespace/uikit"; export function ExternalLabel() { return (
    ); } ``` ### With Description ```tsx import {Checkbox, Description} from "@thenamespace/uikit"; export function WithDescription() { return ( Email notifications Get notified when someone mentions you in a comment ); } ``` ### Render Props ```tsx "use client"; import {Checkbox, Description} from "@thenamespace/uikit"; export function RenderProps() { return ( {({isSelected}) => ( <> {isSelected ? "Terms accepted" : "Accept terms"} {isSelected ? "Thank you for accepting" : "Please read and accept the terms"} )} ); } ``` ### Form Integration ```tsx "use client"; import {Button, Checkbox} from "@thenamespace/uikit"; import React from "react"; export function Form() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.target as HTMLFormElement); alert( `Form submitted with:\n${Array.from(formData.entries()) .map(([key, value]) => `${key}: ${value}`) .join("\n")}`, ); }; return (
    Enable notifications Subscribe to newsletter Receive marketing updates
    ); } ``` ### Invalid ```tsx import {Checkbox, FieldError} from "@thenamespace/uikit"; export function Invalid() { return ( I agree to the terms You must accept the terms to continue ); } ``` ### Custom Indicator ```tsx "use client"; import {Checkbox} from "@thenamespace/uikit"; export function CustomIndicator() { return (
    {({isSelected}) => isSelected ? ( ) : null } Heart {({isSelected}) => isSelected ? ( ) : null } Plus {({isIndeterminate}) => isIndeterminate ? ( ) : null } Indeterminate
    ); } ``` ### Full Rounded ```tsx import {Checkbox, Label} from "@thenamespace/uikit"; export function FullRounded() { return (
    Small size
    Default size
    Large size
    Extra large size
    ); } ``` ### Variants The Checkbox component supports two visual variants: - **`primary`** (default) - Standard styling with default background, suitable for most use cases - **`secondary`** - Lower emphasis variant, suitable for use in Surface components ```tsx import {Checkbox, Description} from "@thenamespace/uikit"; export function Variants() { return (

    Primary variant

    Primary checkbox Standard styling with default background

    Secondary variant

    Secondary checkbox Lower emphasis variant for use in surfaces
    ); } ``` ### Custom Render Function ```tsx "use client"; import {Checkbox, Label} from "@thenamespace/uikit"; export function CustomRenderFunction() { return (
    }>
    ); } ``` ## Styling ### Passing Tailwind CSS classes You can customize individual Checkbox components: ```tsx import { Checkbox } from "@thenamespace/uikit"; function CustomCheckbox() { return ( Custom Checkbox ); } ``` ### Customizing the component classes To customize the Checkbox component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .checkbox { @apply inline-flex gap-3 items-center; } .checkbox__control { @apply size-5 border-2 border-gray-400 rounded data-[selected=true]:bg-blue-500 data-[selected=true]:border-blue-500; /* Animated background indicator */ &::before { @apply bg-accent pointer-events-none absolute inset-0 z-0 origin-center scale-50 rounded-md opacity-0 content-['']; transition: scale 200ms linear, opacity 200ms linear, background-color 200ms ease-out; } /* Show indicator when selected */ &[data-selected="true"]::before { @apply scale-100 opacity-100; } } .checkbox__indicator { @apply text-white; } .checkbox__content { @apply items-center gap-3; } } ``` Namespace UIKit follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The Checkbox component uses these CSS classes: - `.checkbox` - Base checkbox container (the field) - `.checkbox__content` - Clickable label wrapping the control and label text - `.checkbox__control` - Checkbox control box - `.checkbox__indicator` - Checkbox checkmark indicator ### Interactive States The checkbox supports both CSS pseudo-classes and data attributes for flexibility: - **Selected**: `[data-selected="true"]` or `[aria-checked="true"]` (shows checkmark and background color change) - **Indeterminate**: `[data-indeterminate="true"]` (shows indeterminate state with dash) - **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` (shows error state with danger colors) - **Hover**: `:hover` or `[data-hovered="true"]` on `Checkbox.Control` (button) - **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on the button (shows focus ring on control) - **Disabled**: `[data-disabled="true"]` on the field (reduced opacity, including help text) - **Pressed**: `:active` or `[data-pressed="true"]` ## API Reference ### Checkbox Props Inherits from [React Aria CheckboxField](https://react-spectrum.adobe.com/react-aria/Checkbox.html). | Prop | Type | Default | Description | | -------------------- | -------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `isSelected` | `boolean` | `false` | Whether the checkbox is checked | | `defaultSelected` | `boolean` | `false` | Whether the checkbox is checked by default (uncontrolled) | | `isIndeterminate` | `boolean` | `false` | Whether the checkbox is in an indeterminate state | | `isDisabled` | `boolean` | `false` | Whether the checkbox is disabled | | `isInvalid` | `boolean` | `false` | Whether the checkbox is invalid | | `isReadOnly` | `boolean` | `false` | Whether the checkbox is read only | | `isRequired` | `boolean` | `false` | Whether the checkbox must be selected | | `validate` | `(value: boolean) => ValidationError \| true \| null \| undefined` | - | Custom validation function | | `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA | | `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. | | `name` | `string` | - | The name of the input element, used when submitting an HTML form | | `value` | `string` | - | The value of the input element, used when submitting an HTML form | | `onChange` | `(isSelected: boolean) => void` | - | Handler called when the checkbox value changes | | `children` | `React.ReactNode \| (values: CheckboxFieldRenderProps) => React.ReactNode` | - | Checkbox content or field render prop | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### Checkbox.Content Props The clickable `