# Combobox

> Single-value selection primitive that owns value state, input filtering, item registration, and popper positioning while you own the visuals.

Source: https://docs.astra-void.xyz/lattice-ui/components/combobox/

`@lattice-ui/react-combobox` · Stable direction · import `Combobox` · depends on `runtime`, `layer`, `motion`, `popper`

Combobox is the primitive for a filterable, single-value picker: searchable selects, command palettes, and autocomplete fields. It coordinates value state, input text, query filtering, item registration, positioning, and dismissal so your component only has to render the field, the listbox, and the items.

Reach for Combobox when a select needs **type-to-filter** behavior: an input narrows a registered list of items by text, the user picks one, and the chosen value drives the field. Combobox owns three pieces of state at once — the selected **value**, the **input text**, and the **open** state — and keeps them in sync, repairing a selection that no longer resolves to an enabled item so the field does not point at a value that no longer exists.

## Preview

The component running live in the browser — the same `@rbxts/react` tree Roblox renders, fully interactive.

_Interactive preview._

## Import

```ts
import { Combobox } from "@lattice-ui/react-combobox";
```

## Anatomy

Compose the parts you need. `Root`, `Portal`, `Content`, and `Item` form the working picker. Use either `Input` (type-to-filter) or `Trigger` + `Value` (toggle and display) as the anchor, and `Group`, `Label`, and `Separator` to structure longer lists.

```tsx title="Combobox anatomy"
<Combobox.Root>
  <Combobox.Trigger>
    <Combobox.Value />
  </Combobox.Trigger>
  <Combobox.Input />
  <Combobox.Portal>
    <Combobox.Content>
      <Combobox.Label />
      <Combobox.Group>
        <Combobox.Item value="..." />
      </Combobox.Group>
      <Combobox.Separator />
      <Combobox.Item value="..." />
    </Combobox.Content>
  </Combobox.Portal>
</Combobox.Root>
```

| Part | Required | Responsibility |
| --- | --- | --- |
| `Combobox.Root` | yes | Owns value, input, and open state, plus the item registry and filter function. |
| `Combobox.Input` | no | A `TextBox` that opens the list on focus and drives the query as the user types. Acts as the anchor. |
| `Combobox.Trigger` | no | A button that toggles the list and acts as the anchor when no input is present. |
| `Combobox.Value` | no | A label that displays the selected item's text (or a placeholder). |
| `Combobox.Portal` | yes | Renders the listbox into a `ScreenGui` outside the local tree. |
| `Combobox.Content` | yes | The positioned, dismissable, motion-driven listbox. |
| `Combobox.Item` | yes | A selectable option that registers its value and text, and hides itself when filtered out. |
| `Combobox.Group` | no | A container that visually groups related items. |
| `Combobox.Label` | no | A non-interactive heading for a group or section. |
| `Combobox.Separator` | no | A thin divider between items or groups. |

## Examples

### Basic filtered list

An uncontrolled root, an input textbox as the anchor, and items on a plain list surface. Typing narrows the list in place — each item hides itself when it stops matching — and selecting an item sets the value, fills the field, and closes the list.

Since 0.7.0 `textValue` no longer renders as the item's label, so each item is given a `Text` of its own here. `textValue` still drives filtering and the resolved value label; it defaults to `value`.

```tsx title="BiomePicker.tsx"
import { Combobox } from "@lattice-ui/react-combobox";

const BIOMES = ["Ashlands", "Frostpeak", "Gladewood", "Mirelow", "Sunspire"];

export function BiomePicker() {
  return (
    <Combobox.Root defaultValue="Gladewood" onValueChange={(value) => print(`picked: ${value}`)}>
      <Combobox.Input placeholder="Search biomes" />

      <Combobox.Portal>
        <Combobox.Content sideOffset={4}>
          <frame
            AutomaticSize={Enum.AutomaticSize.Y}
            BackgroundColor3={Color3.fromRGB(28, 32, 42)}
            Size={UDim2.fromOffset(240, 0)}
          >
            <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />
            {BIOMES.map((biome) => (
              <Combobox.Item key={biome} Text={biome} value={biome} />
            ))}
          </frame>
        </Combobox.Content>
      </Combobox.Portal>
    </Combobox.Root>
  );
}
```

### Controlled value and input

Control both trios when something outside the combobox needs to read or drive them — persisting the selection, reporting the live query to a search service, or resetting the field from elsewhere. The two pieces are independent: `value`/`onValueChange` tracks the committed selection, while `inputValue`/`onInputValueChange` tracks the field text. Expect `onInputValueChange` to fire for programmatic syncs too — selecting an item sets the input to that item's text, and closing the list re-syncs it from the value.

```tsx title="QuestSearch.tsx"
import { useState } from "@rbxts/react";
import { Combobox } from "@lattice-ui/react-combobox";

const QUESTS = ["Cinder Trial", "Echo Vault", "Gale Run", "Hollow March"];

export function QuestSearch() {
  const [value, setValue] = useState<string>();
  const [inputValue, setInputValue] = useState("");

  return (
    <frame BackgroundTransparency={1} Size={UDim2.fromOffset(260, 80)}>
      <uilistlayout Padding={new UDim(0, 8)} SortOrder={Enum.SortOrder.LayoutOrder} />

      <Combobox.Root
        value={value}
        onValueChange={setValue}
        inputValue={inputValue}
        onInputValueChange={setInputValue}
      >
        <Combobox.Input placeholder="Search quests" />

        <Combobox.Portal>
          <Combobox.Content sideOffset={4}>
            <frame
              AutomaticSize={Enum.AutomaticSize.Y}
              BackgroundColor3={Color3.fromRGB(28, 32, 42)}
              Size={UDim2.fromOffset(240, 0)}
            >
              <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />
              {QUESTS.map((quest) => (
                <Combobox.Item key={quest} Text={quest} value={quest} />
              ))}
            </frame>
          </Combobox.Content>
        </Combobox.Portal>
      </Combobox.Root>

      <textlabel
        BackgroundTransparency={1}
        Size={UDim2.fromOffset(260, 18)}
        Text={value !== undefined ? `Tracking: ${value}` : "No quest tracked"}
        TextColor3={Color3.fromRGB(168, 176, 191)}
        TextXAlignment={Enum.TextXAlignment.Left}
      />
    </frame>
  );
}
```

> **Uncontrolled usage**
>
> Omit `value`/`onValueChange` (and `inputValue`/`open`) and pass `defaultValue`, `defaultInputValue`, or `defaultOpen` instead to let Combobox own its state. Each of value, input text, and open state can be controlled independently — control only the pieces something outside the combobox needs to drive.

### Custom filter function

The root's `filterFn` decides whether each item matches the current query. The default is a case-insensitive substring match; swap it for prefix matching, token search, or locale-aware comparison. Define the function at module scope (or memoize it) so its identity stays stable — it is shared through context, and a new function every render churns every item.

```tsx title="CommandPalette.tsx"
import { Combobox } from "@lattice-ui/react-combobox";
import type { ComboboxFilterFn } from "@lattice-ui/react-combobox";

const COMMANDS = ["Teleport", "Trade", "Track quest", "Toggle HUD"];

const prefixFilter: ComboboxFilterFn = (itemText, query) => {
  return string.sub(string.lower(itemText), 1, query.size()) === string.lower(query);
};

export function CommandPalette() {
  return (
    <Combobox.Root filterFn={prefixFilter} onValueChange={(command) => print(`run: ${command}`)}>
      <Combobox.Input placeholder="Type a command" />

      <Combobox.Portal>
        <Combobox.Content sideOffset={4}>
          <frame
            AutomaticSize={Enum.AutomaticSize.Y}
            BackgroundColor3={Color3.fromRGB(28, 32, 42)}
            Size={UDim2.fromOffset(240, 0)}
          >
            <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />
            {COMMANDS.map((command) => (
              <Combobox.Item key={command} Text={command} value={command} />
            ))}
          </frame>
        </Combobox.Content>
      </Combobox.Portal>
    </Combobox.Root>
  );
}
```

### Groups and labels

`Group`, `Label`, and `Separator` structure longer lists. They are purely visual: they do not affect filtering or selection, and a label is not query-aware — it stays visible even when every item under it is filtered out. All three render unstyled — `Group` is a bare frame with no layout or sizing of its own, so give it a layout and, for a growing list, `AutomaticSize`.

```tsx title="ServerRegionPicker.tsx"
import { Combobox } from "@lattice-ui/react-combobox";

const AMERICAS = ["Chicago", "Dallas", "Sao Paulo"];
const EUROPE = ["Frankfurt", "London", "Warsaw"];

export function ServerRegionPicker() {
  return (
    <Combobox.Root>
      <Combobox.Input placeholder="Search regions" />

      <Combobox.Portal>
        <Combobox.Content sideOffset={4}>
          <frame
            AutomaticSize={Enum.AutomaticSize.Y}
            BackgroundColor3={Color3.fromRGB(28, 32, 42)}
            Size={UDim2.fromOffset(240, 0)}
          >
            <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />

            <Combobox.Group asChild>
              <frame
                AutomaticSize={Enum.AutomaticSize.Y}
                BackgroundTransparency={1}
                Size={UDim2.fromOffset(240, 0)}
              >
                <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />
                <Combobox.Label asChild>
                  <textlabel
                    BackgroundTransparency={1}
                    Size={UDim2.fromOffset(240, 18)}
                    Text="Americas"
                    TextColor3={Color3.fromRGB(168, 176, 191)}
                    TextSize={13}
                    TextXAlignment={Enum.TextXAlignment.Left}
                  />
                </Combobox.Label>
                {AMERICAS.map((region) => (
                  <Combobox.Item key={region} Text={region} value={region} />
                ))}
              </frame>
            </Combobox.Group>

            <Combobox.Separator />

            <Combobox.Group asChild>
              <frame
                AutomaticSize={Enum.AutomaticSize.Y}
                BackgroundTransparency={1}
                Size={UDim2.fromOffset(240, 0)}
              >
                <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />
                <Combobox.Label asChild>
                  <textlabel
                    BackgroundTransparency={1}
                    Size={UDim2.fromOffset(240, 18)}
                    Text="Europe"
                    TextColor3={Color3.fromRGB(168, 176, 191)}
                    TextSize={13}
                    TextXAlignment={Enum.TextXAlignment.Left}
                  />
                </Combobox.Label>
                {EUROPE.map((region) => (
                  <Combobox.Item key={region} Text={region} value={region} />
                ))}
              </frame>
            </Combobox.Group>
          </frame>
        </Combobox.Content>
      </Combobox.Portal>
    </Combobox.Root>
  );
}
```

### Empty state when nothing matches

Filtering hides items one by one — the listbox itself stays open and mounted even when every item is filtered out, and there is no built-in empty-state part. To show a "no matches" message, control `inputValue` and mirror the primitive's filtering with the exported `filterComboboxOptions` helper (it applies `defaultComboboxFilter` unless you pass the same custom `filterFn` you gave the root). When the match count hits zero, render your own label inside the content.

```tsx title="SpellSearch.tsx"
import { useState } from "@rbxts/react";
import { Combobox, filterComboboxOptions } from "@lattice-ui/react-combobox";

const SPELLS = ["Arc Nova", "Ember Coil", "Frost Lattice", "Stone Ward"];
const SPELL_OPTIONS = SPELLS.map((spell) => ({ value: spell, disabled: false, textValue: spell }));

export function SpellSearch() {
  const [inputValue, setInputValue] = useState("");
  const matchCount = filterComboboxOptions(SPELL_OPTIONS, inputValue).size();

  return (
    <Combobox.Root inputValue={inputValue} onInputValueChange={setInputValue}>
      <Combobox.Input placeholder="Search spells" />

      <Combobox.Portal>
        <Combobox.Content sideOffset={4}>
          <frame
            AutomaticSize={Enum.AutomaticSize.Y}
            BackgroundColor3={Color3.fromRGB(28, 32, 42)}
            Size={UDim2.fromOffset(240, 0)}
          >
            <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />
            {SPELLS.map((spell) => (
              <Combobox.Item key={spell} Text={spell} value={spell} />
            ))}
            {matchCount === 0 && (
              <textlabel
                BackgroundTransparency={1}
                Size={UDim2.fromOffset(240, 32)}
                Text="No spells match"
                TextColor3={Color3.fromRGB(153, 161, 177)}
              />
            )}
          </frame>
        </Combobox.Content>
      </Combobox.Portal>
    </Combobox.Root>
  );
}
```

### Custom input styling with asChild

Use `asChild` on `Combobox.Input` to project the input behavior onto your own `textbox`. The primitive drives `Text`, `TextEditable`, `PlaceholderText`, `ClearTextOnFocus`, the text-change handler, and the anchor ref onto your element, so keep the `placeholder` prop on the `Combobox.Input` part (the slot's value wins) and put your styling in colors, strokes, corners, and padding.

```tsx title="StyledCosmeticSearch.tsx"
import { Combobox } from "@lattice-ui/react-combobox";

const COSMETICS = ["Aurora Cape", "Drift Visor", "Ember Trail", "Void Crown"];

export function StyledCosmeticSearch() {
  return (
    <Combobox.Root>
      <Combobox.Input placeholder="Search cosmetics" asChild>
        <textbox
          BackgroundColor3={Color3.fromRGB(24, 26, 32)}
          BorderSizePixel={0}
          Size={UDim2.fromOffset(260, 40)}
          TextColor3={Color3.fromRGB(236, 241, 249)}
          TextSize={15}
          TextXAlignment={Enum.TextXAlignment.Left}
        >
          <uicorner CornerRadius={new UDim(0, 8)} />
          <uistroke Color={Color3.fromRGB(88, 142, 255)} Thickness={1} />
          <uipadding PaddingLeft={new UDim(0, 12)} PaddingRight={new UDim(0, 12)} />
        </textbox>
      </Combobox.Input>

      <Combobox.Portal>
        <Combobox.Content sideOffset={6}>
          <frame
            AutomaticSize={Enum.AutomaticSize.Y}
            BackgroundColor3={Color3.fromRGB(28, 32, 42)}
            Size={UDim2.fromOffset(260, 0)}
          >
            <uicorner CornerRadius={new UDim(0, 8)} />
            <uilistlayout Padding={new UDim(0, 2)} SortOrder={Enum.SortOrder.LayoutOrder} />
            {COSMETICS.map((cosmetic) => (
              <Combobox.Item key={cosmetic} Text={cosmetic} value={cosmetic} />
            ))}
          </frame>
        </Combobox.Content>
      </Combobox.Portal>
    </Combobox.Root>
  );
}
```

## How it behaves

### Open state

`Combobox.Root` is controllable. Pass `open` and `onOpenChange` to control the listbox, or `defaultOpen` to run uncontrolled (defaults to closed). `Combobox.Trigger` toggles the list on activation (and on `Return`/`Space`). `Combobox.Input` opens the list when it gains focus — with an empty query every item matches, so the user sees the full set and then types to narrow it — and typing keeps it open. Selecting an item closes the list. A disabled root refuses to open, but can still close.

### Value and input state

The selected **value** and the **input text** are separate, independently controllable pieces of state. Selecting an item sets the value and syncs the input to that item's display text; selecting a disabled item is ignored. Typing in the input updates the query (and opens the list) without changing the value until a selection is made; when the list closes, the input is re-synced from the current value so the field always shows the selected item.

While the list is open, Combobox reconciles the value against the item registry: a selected value that no longer resolves to an enabled registered item is replaced with the first enabled item. This repair only replaces an invalid selection — it never fills an empty one, so an untouched combobox stays empty until the user picks something. `onValueChange` fires only for defined values.

`Combobox.Value` displays the selected item's text, resolved from the item registry, or its `placeholder` when nothing is selected. `disabled` blocks all state changes; `readOnly` blocks input edits but still allows selection through items.

### Filtering

Filtering is per-item, not per-list. `Combobox.Item` declares a `value` and an optional `textValue` (the text matched and displayed; defaults to `value`), and each item evaluates the root's `filterFn(textValue, query)` itself: a non-matching item sets its own `Visible` to false and becomes non-interactive, both for the button the part renders and for your element under `asChild`. The content never removes or reorders nodes, so your layout simply collapses around hidden items.

The active query is the text the user typed. Selecting an item syncs the field text without changing the open list's query, so the list does not collapse to the selected item at the moment of selection; when the list closes, the query is re-synced from the field text, so reopening from the trigger shows the list filtered by the selected item's text.

The default filter is a case-insensitive plain substring match. The package also exports the pieces as plain functions — `defaultComboboxFilter`, `filterComboboxOptions`, and `resolveComboboxInputValue` — so you can mirror the primitive's filtering in your own logic, such as an empty-state check or an external result count.

### Positioning

`Combobox.Content` is positioned by the popper foundation against the active anchor — the input when present, otherwise the trigger. It flips to the opposite side on collision. Tune it with `placement` (`"top" | "bottom" | "left" | "right"`, default `"bottom"`), `sideOffset` (gap from the anchor, default `0`), `alignOffset` (shift along the cross axis, default `0`), and `collisionPadding` (minimum distance from the screen edge, default `8`).

### Dismissal

`Combobox.Content` participates in dismissable-layer behavior and is always **non-modal**, so the rest of the UI stays interactive while the list is open. The trigger and input are registered as inside refs, so interacting with them does not dismiss the list. Any other outside press closes it. Use `onPointerDownOutside` and `onInteractOutside` to observe or veto those interactions before the list closes.

### Motion and presence

`Combobox.Content` runs no motion of its own. Pass a `transition` to animate it — `createPopperEntranceRecipe(placement)` matches the `frame` the content renders and animates from the resolved placement. `forceMount` keeps the content mounted through its exit (useful when you drive motion yourself or need the node to persist).

> **Three states, kept in sync**
>
> Combobox tracks value, input text, and open state separately and reconciles them automatically: selecting syncs the input, closing re-syncs the input from the value, and an invalid selection is repaired against the registry while the list is open. Control each piece only when you need to — mixing controlled value with uncontrolled input is fully supported.

> **Items hide themselves — there is no built-in empty state**
>
> Filtering works by each item toggling its own visibility, so the listbox stays open and mounted even when nothing matches. Detect zero matches yourself with `filterComboboxOptions` over your own option data (passing your custom `filterFn` if you use one) and render your own empty label, as in the [empty state example](#empty-state-when-nothing-matches).

> **required is wiring-only**
>
> `required` is exposed on context for your own form/validation wiring and does not change interaction on its own. The open-list value repair runs regardless of `required`, and it never fills an empty value — if you need a selection before submit, validate `value !== undefined` in your form logic.

## API reference

### Combobox.Root

| Prop | Type | Description |
| --- | --- | --- |
| `value` | `string` | Controlled selected value. Pair with onValueChange. |
| `defaultValue` | `string` | Initial selected value for uncontrolled usage. |
| `onValueChange` | `(value: string) => void` | Called whenever the selected value changes. Fires only for defined values. |
| `inputValue` | `string` | Controlled input text. Pair with onInputValueChange. |
| `defaultInputValue` | `string` | Initial input text for uncontrolled usage. Defaults to an empty string. |
| `onInputValueChange` | `(inputValue: string) => void` | Called whenever the input text changes, including programmatic syncs from selection and close. |
| `open` | `boolean` | Controlled open state of the listbox. Pair with onOpenChange. |
| `defaultOpen` | `boolean` | Initial open state for uncontrolled usage. Defaults to false. |
| `onOpenChange` | `(open: boolean) => void` | Called whenever the open state changes. |
| `disabled` | `boolean` | Disables the whole combobox, blocking opening, input edits, and selection. Defaults to false. |
| `readOnly` | `boolean` | Blocks input edits while still allowing selection through items. Defaults to false. |
| `required` | `boolean` | Marks the combobox as required for your own form/validation wiring. Exposed on context; does not change interaction on its own. Defaults to false. |
| `filterFn` | `(itemText: string, query: string) => boolean` | Decides whether an item matches the query. Defaults to a case-insensitive substring match. Keep its identity stable. |
| `children` | `React.ReactNode` | The combobox parts. |

### Combobox.Input

| Prop | Type | Description |
| --- | --- | --- |
| `asChild` | `boolean` | Merge input behavior onto the single child element instead of the textbox the part renders. The child must be a textbox. |
| `disabled` | `boolean` | Disables the input. Combined with the root's disabled state. |
| `readOnly` | `boolean` | Blocks edits to the input text. Combined with the root's readOnly state. |
| `placeholder` | `string` | Placeholder text shown when the input is empty. Defaults to "Type to filter". |
| `children` | `React.ReactElement` | The element to render. Required when asChild is set. |

### Combobox.Trigger

| Prop | Type | Description |
| --- | --- | --- |
| `asChild` | `boolean` | Merge trigger behavior onto the single child element instead of the textbutton the part renders. |
| `disabled` | `boolean` | Prevents the trigger from toggling the list. Combined with the root's disabled state. |
| `children` | `React.ReactElement` | The element to render. Required when asChild is set. |

### Combobox.Value

| Prop | Type | Description |
| --- | --- | --- |
| `asChild` | `boolean` | Merge the resolved value onto the single child element instead of the textlabel the part renders. |
| `placeholder` | `string` | Text shown when no value is selected. Defaults to an empty string. |
| `children` | `React.ReactElement` | The element to render. Required when asChild is set. |

### Combobox.Portal

| Prop | Type | Description |
| --- | --- | --- |
| `container` | `BasePlayerGui` | Target PlayerGui to render the listbox into. Defaults to the surrounding portal context's container. |
| `displayOrderBase` | `number` | Base DisplayOrder for the generated ScreenGui, used to order it against other layers. Defaults to the surrounding portal context's value. |
| `children` | `React.ReactNode` | The content part. |

### Combobox.Content

| Prop | Type | Description |
| --- | --- | --- |
| `placement` | `"top" \| "bottom" \| "left" \| "right"` | Requested side to position the listbox on. Flips on collision. Defaults to "bottom". |
| `sideOffset` | `number` | Gap in pixels between the anchor and the listbox. Defaults to 0. |
| `alignOffset` | `number` | Shift in pixels along the anchor's cross axis. Defaults to 0. |
| `collisionPadding` | `number` | Minimum distance in pixels to keep from the screen edge. Defaults to 8. |
| `asChild` | `boolean` | Render the single child element inside the positioned wrapper instead of the frame the part renders. |
| `forceMount` | `boolean` | Keeps the listbox mounted while exit motion runs. |
| `transition` | `PresenceMotionConfig` | Reveal/exit motion. None by default; pass createPopperEntranceRecipe(placement) for a placement-aware entrance. |
| `onPointerDownOutside` | `(event: LayerInteractEvent) => void` | Called when a pointer press occurs outside the listbox, before dismissal. |
| `onInteractOutside` | `(event: LayerInteractEvent) => void` | Called for any other outside interaction, before dismissal. |
| `children` | `React.ReactNode` | The listbox contents. |

### Combobox.Item

| Prop | Type | Description |
| --- | --- | --- |
| `value` | `string` | Required. The value selected when this item is chosen. |
| `textValue` | `string` | Text used for filtering and for the resolved value label. Defaults to value. Since 0.7.0 it does not render as the item's own label — supply that as a child or via a forwarded Text prop. |
| `disabled` | `boolean` | Prevents selection and excludes the item from open-list value repair. |
| `asChild` | `boolean` | Merge item behavior onto the single child element instead of the textbutton the part renders. The child's Visible property is bound to the filter match. |
| `children` | `React.ReactElement` | The element to render. Required when asChild is set. |

### Combobox.Group

| Prop | Type | Description |
| --- | --- | --- |
| `asChild` | `boolean` | Merge the group onto the single child element instead of the frame the part renders. |
| `children` | `React.ReactElement` | The grouped items to render. Required when asChild is set. |

### Combobox.Label

| Prop | Type | Description |
| --- | --- | --- |
| `asChild` | `boolean` | Merge the label onto the single child element instead of the textlabel the part renders. |
| `children` | `React.ReactElement` | The label element to render. Required when asChild is set. |

### Combobox.Separator

| Prop | Type | Description |
| --- | --- | --- |
| `asChild` | `boolean` | Merge the separator onto the single child element instead of the frame the part renders. |
| `children` | `React.ReactElement` | The divider element to render. Required when asChild is set. |

## Related

- [Positioning with popper](https://docs.astra-void.xyz/lattice-ui/guides/positioning-with-popper.md)
- [Portals and layers](https://docs.astra-void.xyz/lattice-ui/guides/portals-and-layers.md)
- [Controlled state](https://docs.astra-void.xyz/lattice-ui/guides/controlled-state.md)
- [Presence and motion](https://docs.astra-void.xyz/lattice-ui/guides/presence-and-motion.md)
- [asChild composition](https://docs.astra-void.xyz/lattice-ui/guides/as-child-composition.md)
