Circle V2 API Docs
    Preparing search index...

    Module @repo/ui

    @repo/ui

    The CircleHealth design system, built on Chakra UI v3.

    Every component in src/ is individually importable via subpath exports defined in package.json:

    "exports": {
    "./*": "./src/*.tsx", // @repo/ui/Button, @repo/ui/Dialog, etc.
    "./icons": "./src/icons/index.tsx",
    "./composites":"./src/composites/index.ts",
    "./hooks": "./src/hooks/index.ts",
    "./tests": "./__tests__/mocks/index.ts"
    }
    import { Button } from "@repo/ui/Button";
    import { Table } from "@repo/ui/Table";
    import { DataTable } from "@repo/ui/composites";
    import { useDebounce } from "@repo/ui/hooks";
    import { SearchIcon } from "@repo/ui/icons";

    Components are organized into two tiers:

    Atoms (src/*.tsx) are the core building blocks -- one file per component at the root of src/. They are either Chakra re-exports or thin wrappers (see Component Patterns below). Import them directly:

    import { Card } from "@repo/ui/Card";
    import { Field } from "@repo/ui/Field";

    Composites (src/composites/) are higher-level components that compose multiple atoms and add significant behavior such as state management or third-party integrations. Each composite gets its own directory and is imported from the composites subpath:

    import { DataTable, MultiFilterMenu } from "@repo/ui/composites";
    

    Composites may contain their own internal atoms (e.g. MultiFilterMenu/atoms/) -- sub-pieces that are only meaningful within that composite.

    We use two strategies for wrapping Chakra, depending on how much customization a component needs.

    For components where we don't need to customize behavior, we re-export directly from Chakra. This gives us a single import surface across the app and the option to add customizations later without changing consumer code.

    // Box.tsx
    export { Box, type BoxProps } from "@chakra-ui/react";

    Other examples: Text, Heading, Stack, Separator, Center, Kbd.

    For components that need custom defaults, added behavior, or API sugar, we wrap the Chakra component:

    Component What the wrapper adds
    Button Analytics tracking on click
    Card Default rounded: "xl" via styled()
    Field Convenience props (label, helperText, errorText, optionalText)
    Tabs Default colorPalette on Trigger
    Input Explicit ref typing
    SearchInput Composes InputGroup + Input + search icon
    Dialog Layout with trigger, title, description, footer props

    Components expose one of two API styles -- or sometimes both.

    The consumer assembles the UI from subcomponents. This gives maximum flexibility over layout and behavior.

    <Table.Root>
    <Table.Header>
    <Table.Row>
    <Table.HeadCell>Name</Table.HeadCell>
    </Table.Row>
    </Table.Header>
    <Table.Body>
    <Table.Row>
    <Table.Cell>Alice</Table.Cell>
    </Table.Row>
    </Table.Body>
    </Table.Root>

    Other composed-API components: Tabs, Card, Dialog.*, MultiFilterMenu.*.

    The consumer passes data or config props and the component renders its own tree. Simpler to use for common cases.

    <DataTable data={patients} columns={columns} onSortingChange={setSorting} />
    
    <EmptyState
    title="No results"
    description="Try a different search"
    icon={<SearchIcon />}
    />

    We provide contained APIs when a Chakra compound component has a "default" layout that gets repeated across the app. Rather than asking every consumer to compose the same subtree of Root > Content > Indicator > Title > Description, the contained variant accepts config props and assembles that tree internally. This keeps call sites focused on data, not structure. When the default layout doesn't fit, the composed subcomponents are always available as an escape hatch (see Both at once below).

    Creating a contained component -- here's the pattern, using EmptyState as an example:

    // 1. Define config props that extend the Chakra root props
    type EmptyStateProps = ChakraEmptyState.RootProps & {
    title: string;
    description?: string;
    icon?: React.ReactNode;
    };

    // 2. Build the Chakra subtree internally from those props
    function BaseEmptyState({
    title,
    description,
    icon,
    children,
    ...rest
    }: EmptyStateProps) {
    return (
    <ChakraEmptyState.Root {...rest}>
    <ChakraEmptyState.Content>
    {icon && (
    <ChakraEmptyState.Indicator>{icon}</ChakraEmptyState.Indicator>
    )}
    <ChakraEmptyState.Title>{title}</ChakraEmptyState.Title>
    {description && (
    <ChakraEmptyState.Description>
    {description}
    </ChakraEmptyState.Description>
    )}
    {children}
    </ChakraEmptyState.Content>
    </ChakraEmptyState.Root>
    );
    }

    // 3. Attach subcomponents so the composed API remains accessible
    export const EmptyState = Object.assign(BaseEmptyState, {
    Root: ChakraEmptyState.Root,
    Content: ChakraEmptyState.Content,
    Title: ChakraEmptyState.Title,
    Description: ChakraEmptyState.Description,
    Indicator: ChakraEmptyState.Indicator,
    });

    Some components offer a convenient contained default and expose composed subcomponents via Object.assign. Dialog and MultiFilterMenu both follow this pattern:

    // Contained -- pass props, get a complete dialog
    <Dialog trigger={<Button>Open</Button>} title="Confirm" footer={<Button>Save</Button>}>
    Are you sure?
    </Dialog>

    // Composed -- full control over every piece
    <Dialog.Root>
    <Dialog.Trigger asChild><Button>Open</Button></Dialog.Trigger>
    <Dialog.Backdrop />
    <Dialog.Positioner>
    <Dialog.Content>
    <Dialog.Title>Confirm</Dialog.Title>
    <Dialog.Body>Are you sure?</Dialog.Body>
    </Dialog.Content>
    </Dialog.Positioner>
    </Dialog.Root>

    All icons are exported from the @repo/ui/icons subpath:

    import { SearchIcon, CloseIcon, FilterIcon } from "@repo/ui/icons";
    

    Icons wrap Lucide SVGs using a createIconComponent helper. Each icon renders the Lucide SVG inside the design-system Icon component, so every icon accepts standard IconProps (Chakra icon props + ref).

    We also define semantic aliases -- multiple exports can point to the same underlying Lucide glyph when it makes sense in different contexts (e.g. SortDescendingIcon and ArrowDownIcon both use ArrowDown). This keeps usage self-documenting without duplicating SVGs.

    Several @repo/ui components have built-in react-hook-form support. When you pass control + name (from @repo/use-form), the component automatically wires into your form via Controller -- no extra imports or manual render prop needed.

    Components with built-in hook-form support: Checkbox, Switch, Select, SegmentedControl, PinInput, MultiFilterMenu.

    import { useForm } from "@repo/use-form";
    import { Checkbox } from "@repo/ui/Checkbox";
    import { Select } from "@repo/ui/Select";

    const { control } = useForm({ schema: mySchema });

    // Just pass control + name -- the component handles Controller internally
    <Checkbox control={control} name="agreeToTerms">
    I agree to the terms
    </Checkbox>

    <Select control={control} name="role" items={roleItems} placeholder="Select a role" />

    Without hook-form, the same components work as normal controlled/uncontrolled inputs:

    <Checkbox
    checked={agreed}
    onCheckedChange={({ checked }) => setAgreed(checked)}
    >
    I agree to the terms
    </Checkbox>

    When a component is inside a form managed by useForm from @repo/use-form, prefer passing control + name directly rather than manually wiring Controller. This keeps form code concise and ensures field names are type-safe (inferred from the Zod schema).

    The exported component uses a discriminated union on its props -- when control is present, it delegates to an internal HookFormX function. That function renders Controller from @repo/use-form/internals and maps the RHF field object to the component's value/change API. composeRef from the same package merges the caller's ref with RHF's field.ref where needed.

    Creating a new hook-form variant follows this pattern:

    import {
    Controller,
    type ControllerProps,
    type FieldValues,
    type FieldPath,
    } from "@repo/use-form/internals";

    // 1. Define HookForm props -- ControllerProps minus `render`, plus the base component props
    type HookFormMyInputProps<
    TFieldValues extends FieldValues,
    TName extends FieldPath<TFieldValues>,
    > = Omit<ControllerProps<TFieldValues, TName>, "render"> & MyInputProps;

    // 2. Implement the HookForm variant using Controller
    function HookFormMyInput<
    TFieldValues extends FieldValues,
    TName extends FieldPath<TFieldValues>,
    >({ control, name, ...props }: HookFormMyInputProps<TFieldValues, TName>) {
    return (
    <Controller
    control={control}
    name={name}
    render={({ field }) => (
    <BaseMyInput
    {...props}
    value={field.value}
    onChange={field.onChange}
    onBlur={field.onBlur}
    />
    )}
    />
    );
    }

    // 3. Branch in the main export based on whether `control` is present
    export function MyInput(props: MyInputProps | HookFormMyInputProps<any, any>) {
    if ("control" in props) {
    return <HookFormMyInput {...props} />;
    }
    return <BaseMyInput {...props} />;
    }
    %%{init:{"theme":"dark"}}%% graph TD ui["@repo/ui"] utils["@repo/utils"] use_form["@repo/use-form"] analytics_core["@repo/analytics-core"] analytics_react["@repo/analytics-react"] typescript_config["@repo/typescript-config"] vitest_config["@repo/vitest-config"] ui --> utils ui --> use_form ui -.-> analytics_core ui -.-> analytics_react ui -.-> typescript_config ui -.-> vitest_config
    %%{init:{"theme":"default"}}%% graph TD ui["@repo/ui"] utils["@repo/utils"] use_form["@repo/use-form"] analytics_core["@repo/analytics-core"] analytics_react["@repo/analytics-react"] typescript_config["@repo/typescript-config"] vitest_config["@repo/vitest-config"] ui --> utils ui --> use_form ui -.-> analytics_core ui -.-> analytics_react ui -.-> typescript_config ui -.-> vitest_config
    graph TD
      ui["@repo/ui"]
      utils["@repo/utils"]
      use_form["@repo/use-form"]
      analytics_core["@repo/analytics-core"]
      analytics_react["@repo/analytics-react"]
      typescript_config["@repo/typescript-config"]
      vitest_config["@repo/vitest-config"]
      ui --> utils
      ui --> use_form
      ui -.-> analytics_core
      ui -.-> analytics_react
      ui -.-> typescript_config
      ui -.-> vitest_config
    Script Description
    lint Run Biome checks
    check-types Typecheck with tsc --noEmit
    chakra:typegen Regenerate Chakra type tokens
    test Run Vitest with coverage
    test:watch Run Vitest in watch mode

    Modules

    AbsoluteCenter
    Alert
    Avatar
    Badge
    Box
    Button
    Button.test
    Card
    Center
    chakra.system
    Chart
    Checkbox
    ClientOnly
    Collapsible
    Combobox
    composites
    composites/CopyIconButton/CopyIconButton
    composites/CopyIconButton/CopyIconButton.test
    composites/DataTable/DataTable
    composites/DataTable/DataTable.test
    composites/InlineRichTextEditor/InlineRichTextEditor
    composites/InlineRichTextEditor/InlineRichTextEditor.test
    composites/MarkdownRenderer/MarkdownRenderer
    composites/MarkdownRenderer/MarkdownRenderer.test
    composites/MultiFilterMenu/atoms/MultiFilterMenuCategoryItem
    composites/MultiFilterMenu/atoms/MultiFilterMenuCategoryList
    composites/MultiFilterMenu/atoms/MultiFilterMenuContainedPanels
    composites/MultiFilterMenu/atoms/MultiFilterMenuContent
    composites/MultiFilterMenu/atoms/MultiFilterMenuDateRangePanel
    composites/MultiFilterMenu/atoms/MultiFilterMenuFooter
    composites/MultiFilterMenu/atoms/MultiFilterMenuMultiSelectPanel
    composites/MultiFilterMenu/atoms/MultiFilterMenuPanel
    composites/MultiFilterMenu/atoms/MultiFilterMenuRoot
    composites/MultiFilterMenu/atoms/MultiFilterMenuSelectPanel
    composites/MultiFilterMenu/atoms/MultiFilterMenuTrigger
    composites/MultiFilterMenu/atoms/MultiFilterMenuTriggerCountBadge
    composites/MultiFilterMenu/context
    composites/MultiFilterMenu/MultiFilterMenu
    composites/MultiFilterMenu/MultiFilterMenu.test
    composites/RichTextEditor/RichTextEditor
    composites/RichTextEditor/RichTextEditor.test
    composites/RichTextEditor/SelectionBubble
    composites/RichTextEditor/Toolbar
    composites/SingleDatePicker/SingleDatePicker
    composites/TablePagination/TablePagination
    composites/TablePagination/TablePagination.test
    DataList
    DataList.test
    DatePicker
    Dialog
    Drawer
    Drawer.test
    Editable
    EmptyState
    Field
    Field.test
    FileUpload
    Float
    Group
    Heading
    hooks
    hooks/useLoadMoreSentinel.test
    HoverCard
    Icon
    IconButton
    icons
    icons/index
    InfiniteLoader
    InfiniteLoader.test
    Input
    InputGroup
    Kbd
    List
    LoadMoreSentinel
    LoadMoreSentinel.test
    MorphingGradient
    NativeSelect
    NativeSelect.test
    Pagination
    PinInput
    Popover
    SearchInput
    SegmentedControl
    Select
    Select.test
    Separator
    Skeleton
    Spinner
    Splitter
    Stack
    styled
    Switch
    Table
    Tabs
    Tag
    Text
    Textarea
    Theme
    ThemeProvider
    ThemeToggleButton
    ThemeToggleButton.test
    Timeline
    Toast
    Tooltip
    useTheme
    useTheme.test
    WaveformAudioPlayer