suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
  • AI
GitHubCreate
DocsComponentsPatternsInteractionsChartsBlocksAICreate
Components84
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Code Block
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Trace
  • Wheel Picker
  • Widget

suiss/ui · Component

Combobox

A text input with a filterable list of options, for choosing one or more values by typing.

Combobox is an open-source component from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"

const frameworks = ["Next.js", "Remix", "Astro", "SvelteKit", "Nuxt", "Gatsby"]

export default function ComboboxDemo() {
  return (
    <div className="w-72">
      <Combobox items={frameworks}>
        <ComboboxInput placeholder="Select a framework" disabled={false} />

Combobox is built on the Base UI Combobox. The Combobox root takes the items to choose from and holds the value. ComboboxInput renders the text field inside an InputGroup, with an optional button that opens the list and an optional clear button. ComboboxContent renders the popup in a portal; inside it, ComboboxList renders each item through a render function, ComboboxEmpty appears when nothing matches, and ComboboxItem shows a check mark when selected. Group related items with ComboboxGroup, ComboboxLabel, ComboboxCollection and ComboboxSeparator.

For multiple selection, replace ComboboxInput with ComboboxChips, which shows each selected value as a ComboboxChip next to a ComboboxChipsInput. Use a combobox when the list is long enough that typing to filter helps. For a short, fixed list without filtering, use a select; for a list of actions rather than values, use a command menu.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/combobox

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add combobox

Manual

  1. Install the dependencies and add the cn helper:
$ pnpm add @base-ui/react @phosphor-icons/react
  1. Add the pieces it builds on: Button, Input Group.

  2. Copy the source into components/ui/combobox.tsx:

components/ui/combobox.tsx
"use client"

import { Combobox as ComboboxPrimitive } from "@base-ui/react"
import { CaretDownIcon, CheckIcon, XIcon } from "@phosphor-icons/react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group"
import { cn } from "@/lib/utils"

const Combobox = ComboboxPrimitive.Root
  1. Update the import paths to match your project.

Usage

import { ComboboxValue, ComboboxTrigger, ComboboxInput, ComboboxContent, ComboboxList, ComboboxItem, ComboboxGroup, ComboboxLabel, ComboboxCollection, ComboboxEmpty, ComboboxSeparator, ComboboxChips, ComboboxChip, ComboboxChipsInput } from "@/components/ui/combobox"
<div className="w-72">
  <Combobox items={frameworks}>
    <ComboboxInput
      placeholder="Select a framework"
      disabled={false}
    />
    <ComboboxContent>
      <ComboboxEmpty>No framework found.</ComboboxEmpty>
      <ComboboxList>
        {(item: string) => (
          <ComboboxItem key={item} value={item}>
            {item}
          </ComboboxItem>
        )}
      </ComboboxList>
    </ComboboxContent>
  </Combobox>
</div>

Pass items to the root and a function as the child of ComboboxList, which receives each item and returns a ComboboxItem with a matching value. The value is uncontrolled with defaultValue, or controlled with value and onValueChange. To read or control the typed text, use inputValue and onInputValueChange.

For grouped data, pass groups as items, render a ComboboxGroup with its own items for each one, and render its items with ComboboxCollection. For multiple selection, set multiple on the root, create a ref with useComboboxAnchor, attach it to ComboboxChips, and pass it as anchor to ComboboxContent so the popup aligns to the chips field. Use ComboboxValue with a render function to map the selected values to chips.

Examples

With clear button

A combobox with a starting value and a clear button, enabled with showClear.

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"

const frameworks = ["Next.js", "Remix", "Astro", "SvelteKit", "Nuxt", "Gatsby"]

export default function ComboboxWithClearButton() {
  return (
    <div className="w-72">
      <Combobox items={frameworks} defaultValue="Astro">
        <ComboboxInput placeholder="Select a framework" showClear />

Disabled

A disabled combobox whose input and toggle button cannot be focused or used.

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"

const frameworks = ["Next.js", "Remix", "Astro", "SvelteKit", "Nuxt", "Gatsby"]

export default function ComboboxDisabled() {
  return (
    <div className="w-72">
      <Combobox disabled items={frameworks}>
        <ComboboxInput placeholder="Select a framework" disabled={true} />

Grouped

Options grouped by region with labels and separators between groups.

import {
  Combobox,
  ComboboxCollection,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxInput,
  ComboboxItem,
  ComboboxLabel,
  ComboboxList,
  ComboboxSeparator,
} from "@/components/ui/combobox"

const timezones = [
  {
    value: "Americas",

Highlight match

A controlled inputValue used to emphasize the matching part of each option.

import * as React from "react"
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"

const frameworks = ["Next.js", "Remix", "Astro", "SvelteKit", "Nuxt", "Gatsby"]

function HighlightedMatch({ text, query }: { text: string; query: string }) {
  const start = query ? text.toLowerCase().indexOf(query.toLowerCase()) : -1
  if (start < 0) return text
  return (

Multiple

Multiple selection shown as removable chips, with Backspace removing the last chip.

import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxChipsInput,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxValue,
  useComboboxAnchor,
} from "@/components/ui/combobox"

const frameworks = ["Next.js", "Remix", "Astro", "SvelteKit", "Nuxt", "Gatsby"]

function MultipleCombobox({ disabled }: { disabled?: boolean }) {

API Reference

Anatomy11 parts
<Combobox>
<ComboboxInput />
<ComboboxContent>
<ComboboxEmpty />
<ComboboxList>
<ComboboxItem />
<ComboboxGroup>
<ComboboxSeparator />
<ComboboxLabel />
<ComboboxCollection>
<ComboboxItem />
</ComboboxCollection>
</ComboboxGroup>
</ComboboxList>
</ComboboxContent>
</Combobox>
01

Combobox

The root that holds the items, the selected value and the open state.

Also accepts the props of ComboboxPrimitive.Root.

02

ComboboxValue

Renders the current value, or passes it to a render function, for example to draw chips.

Also accepts the props of ComboboxPrimitive.Value.Props.

03

ComboboxTrigger

A button that opens the list, with a caret icon.

Also accepts the props of ComboboxPrimitive.Trigger.Props.

04

ComboboxInput

The text field, rendered inside an InputGroup with optional open and clear buttons.

PropTypeDefaultDescription
showTriggerbooleantrueShows the button at the end of the field that opens the list.
showClearbooleanfalseShows a clear button at the end of the field. When shown, it hides the open button.

Also accepts the props of ComboboxPrimitive.Input.Props.

05

ComboboxContent

The popup, rendered in a portal and positioned below the input.

Also accepts the props of ComboboxPrimitive.Popup.Props, ComboboxPrimitive.Positioner.Props.

06

ComboboxList

The scrollable list that renders items through a render function.

Also accepts the props of ComboboxPrimitive.List.Props.

07

ComboboxItem

A selectable option that shows a check mark when selected.

Also accepts the props of ComboboxPrimitive.Item.Props.

08

ComboboxGroup

Groups related options and takes its own items.

Also accepts the props of ComboboxPrimitive.Group.Props.

09

ComboboxLabel

The visible label of a group.

Also accepts the props of ComboboxPrimitive.GroupLabel.Props.

10

ComboboxCollection

Renders the items of a group through a render function.

Also accepts the props of ComboboxPrimitive.Collection.Props.

11

ComboboxEmpty

Content shown when no options match the typed text.

Also accepts the props of ComboboxPrimitive.Empty.Props.

12

ComboboxSeparator

A divider between groups.

Also accepts the props of ComboboxPrimitive.Separator.Props.

13

ComboboxChips

The field that holds selected values as chips in multiple selection.

Also accepts the props of React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips>, ComboboxPrimitive.Chips.Props.

14

ComboboxChip

A selected value shown as a chip, with an optional remove button.

PropTypeDefaultDescription
showRemovebooleantrueShows a button inside the chip that removes the value.
removeLabelstringRemove {value}Accessible name of the remove button. Defaults to Remove followed by the chip text.

Also accepts the props of ComboboxPrimitive.Chip.Props.

15

ComboboxChipsInput

The text field placed after the chips in multiple selection.

Also accepts the props of ComboboxPrimitive.Input.Props.

Accessibility

  • The input has the combobox role and sets aria-expanded to reflect whether the list is open.
  • The popup has the listbox role, options have the option role and selected options set aria-selected; groups have the group role and take their name from ComboboxLabel.
  • Typing filters the options. Arrow keys move the highlight, Enter selects the highlighted option, and Escape closes the list.
  • In multiple selection, Backspace in the empty input removes the last chip.
  • The open button is labelled Show options and the clear button is labelled Clear.
  • When disabled, the input and the open button are disabled and removed from the Tab order.
  • Give the input an accessible name with a visible label or aria-label.

Notes

  • Styling hooks: data-slot values such as combobox-content, combobox-list, combobox-item, combobox-label, combobox-empty, combobox-chips, combobox-chip, combobox-chip-remove and combobox-chip-input.
  • Highlighted items get data-highlighted and disabled items get data-disabled.
  • The popup matches the width of its anchor through --anchor-width and limits its height to the available space.
  • Uses the popover, accent, primary, secondary and border colors.
Web

Web preview

import * as React from "react"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

function ComboboxDemo({
  defaultValue = null,
  defaultOpen,
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox

@Composable
fun ComboboxOpenByDefaultExample() {
    ComboboxDemo(defaultValue = "Expo")

Combobox pairs a text input with a floating list that filters its items as the user types. ComboboxInput draws the field with a caret trigger and an optional clear button, and ComboboxContent renders the list through a portal (inside a full-window overlay on iOS), positioned below the input and as wide as it.

For multiple selection, set multiple and use ComboboxChips with a ComboboxChip per selected value and a ComboboxChipsInput for typing. Each chip has a remove button.

Installation

$ pnpm dlx shadcn@latest add @esuiss-rn/combobox

See Installation for the components.json setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add combobox
$ pnpm dlx @esuiss/ui@latest add combobox --platform swift
$ pnpm dlx @esuiss/ui@latest add combobox --platform kotlin

The CLI also adds what it builds on: Button, Icon, Text. On React Native it also adds the animated view components/ui/animated-view.tsx.

Manual

  1. Install the dependencies:
npx expo install cn phosphor-react-native react-native-reanimated react-native-screens react-native-svg react-native-worklets
  1. Add the pieces it builds on: Button, Icon, Text, and the primitives components/primitives/combobox.tsx, components/primitives/field.tsx, components/primitives/positioner.tsx, components/primitives/use-render.tsx, and the animated view components/ui/animated-view.tsx from the next step.

  2. Copy the animated view into components/ui/animated-view.tsx:

uniwind/components/ui/animated-view.tsx
import { Pressable } from "react-native"
import Animated from "react-native-reanimated"

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

type AnimatedViewProps =
  | (React.ComponentProps<typeof Animated.View> &
      React.RefAttributes<React.ComponentRef<typeof Animated.View>> & {
        as?: "View"
      })
  | (React.ComponentProps<typeof AnimatedPressable> &
      React.RefAttributes<React.ComponentRef<typeof AnimatedPressable>> & {
        as: "Pressable"
      })

function AnimatedView(props: AnimatedViewProps) {
  1. Copy the source into components/ui/combobox.tsx:
uniwind/components/ui/combobox.tsx
import { cn } from "cn"
import { CaretDownIcon, CheckIcon, XIcon } from "phosphor-react-native"
import * as React from "react"
import {
  Platform,
  type StyleProp,
  type View,
  type ViewStyle,
} from "react-native"
import { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as ComboboxPrimitive from "@/components/primitives/combobox"
import { useFieldRootContext } from "@/components/primitives/field"
import { useOptionalPositionerContext } from "@/components/primitives/positioner"
import {
  resolveClassName,
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install cn nativewind phosphor-react-native react-native-reanimated react-native-screens react-native-svg react-native-worklets
  1. Add the pieces it builds on: Button, Icon, Text, and the primitives components/primitives/combobox.tsx, components/primitives/field.tsx, components/primitives/positioner.tsx, components/primitives/use-render.tsx, and the animated view components/ui/animated-view.tsx from the next step.

  2. Copy the animated view into components/ui/animated-view.tsx:

nativewind/components/ui/animated-view.tsx
import { Pressable } from "react-native"
import Animated from "react-native-reanimated"

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

type AnimatedViewProps =
  | (React.ComponentProps<typeof Animated.View> &
      React.RefAttributes<React.ComponentRef<typeof Animated.View>> & {
        as?: "View"
      })
  | (React.ComponentProps<typeof AnimatedPressable> &
      React.RefAttributes<React.ComponentRef<typeof AnimatedPressable>> & {
        as: "Pressable"
      })

function AnimatedView(props: AnimatedViewProps) {
  1. Copy the source into components/ui/combobox.tsx:
nativewind/components/ui/combobox.tsx
import { cn } from "cn"
import { cssInterop } from "nativewind"
import { CaretDownIcon, CheckIcon, XIcon } from "phosphor-react-native"
import * as React from "react"
import {
  Platform,
  type StyleProp,
  TextInput,
  type View,
  type ViewStyle,
} from "react-native"
import { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as ComboboxPrimitive from "@/components/primitives/combobox"
import { useFieldRootContext } from "@/components/primitives/field"
import { useOptionalPositionerContext } from "@/components/primitives/positioner"
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install phosphor-react-native react-native-reanimated react-native-screens react-native-svg react-native-worklets
  1. Add the pieces it builds on: Button, Icon, Text, and the primitives components/primitives/combobox.tsx, components/primitives/field.tsx, components/primitives/positioner.tsx, components/primitives/use-render.tsx, and the animated view components/ui/animated-view.tsx from the next step.

  2. Copy the animated view into components/ui/animated-view.tsx:

stylesheet/components/ui/animated-view.tsx
import { Pressable } from "react-native"
import Animated from "react-native-reanimated"

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

type AnimatedViewProps =
  | (React.ComponentProps<typeof Animated.View> &
      React.RefAttributes<React.ComponentRef<typeof Animated.View>> & {
        as?: "View"
      })
  | (React.ComponentProps<typeof AnimatedPressable> &
      React.RefAttributes<React.ComponentRef<typeof AnimatedPressable>> & {
        as: "Pressable"
      })

function AnimatedView(props: AnimatedViewProps) {
  1. Copy the source into components/ui/combobox.tsx:
stylesheet/components/ui/combobox.tsx
import { CaretDownIcon, CheckIcon, XIcon } from "phosphor-react-native"
import * as React from "react"
import {
  Platform,
  type StyleProp,
  StyleSheet,
  type TextStyle,
  type View,
  type ViewStyle,
} from "react-native"
import { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as ComboboxPrimitive from "@/components/primitives/combobox"
import { useFieldRootContext } from "@/components/primitives/field"
import { useOptionalPositionerContext } from "@/components/primitives/positioner"
import { resolveStyle } from "@/components/primitives/use-render"
  1. Update the import paths to match your project.
  1. Target iOS 17 or later.

  2. Add the Swift package to your app:

Package.swift
.package(url: "https://github.com/phosphor-icons/swift", from: "2.1.0")
  1. Copy the theme files every view uses:
swift/Theme/Theme.swift
import CoreText
import SwiftUI
import UIKit

struct RGBA: Sendable {
  let red: CGFloat
  let green: CGFloat
  let blue: CGFloat
  let alpha: CGFloat

  init(_ hex: UInt32, _ alpha: CGFloat = 1) {
    red = CGFloat((hex >> 16) & 0xFF) / 255
    green = CGFloat((hex >> 8) & 0xFF) / 255
    blue = CGFloat(hex & 0xFF) / 255
    self.alpha = alpha
  }
swift/Theme/Tokens.swift
import SwiftUI

public enum EsuissColor {
  public static let background = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
  public static let foreground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let card = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let cardForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let popover = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let popoverForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let primary = Color.adaptive(light: .init(0x0071E3), dark: .init(0x2997FF))
  public static let primaryForeground = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0xFFFFFF))
  public static let secondary = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
  public static let secondaryForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let muted = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
  public static let mutedForeground = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
  public static let accent = Color.adaptive(light: .init(0x000000, 0.06), dark: .init(0xFFFFFF, 0.1))
  1. Copy the source and every file it uses:
swift/Components/Combobox.swift
import PhosphorSwift
import SwiftUI

public struct Combobox<Item: Hashable & Sendable>: View {
  let items: [Item]
  let single: Binding<Item?>?
  let multiple: Binding<[Item]>?
  let placeholder: LocalizedStringKey
  let searchPlaceholder: LocalizedStringKey
  let emptyText: LocalizedStringKey
  let title: (Item) -> String

  @State private var open = false
  @Environment(\.isEnabled) private var isEnabled
  @Environment(\.isInvalid) private var invalid
  @Environment(\.colorScheme) private var colorScheme
swift/Components/Button.swift
import SwiftUI

public struct VariantButtonStyle: ButtonStyle {
  public enum Variant: CaseIterable, Sendable {
    case `default`, outline, secondary, tinted, ghost, plain, destructive, link
  }

  public enum Size: CaseIterable, Sendable {
    case `default`, xs, sm, lg, xl, icon, iconXs, iconSm, iconLg, iconXl

    var height: CGFloat {
      switch self {
      case .xs, .iconXs: Metrics.Height.buttonXs
      case .sm, .iconSm: Metrics.Height.buttonSm
      case .default, .icon: Metrics.Height.button
      case .lg, .iconLg: Metrics.Height.buttonLg
swift/Components/ButtonGroup.swift
import SwiftUI

extension EnvironmentValues {
  @Entry var buttonGroup: Axis? = nil
  @Entry var buttonGroupSize: CGSize = .zero
}

enum ButtonGroupSpace: Hashable {
  case group
}

struct ButtonGroupFrame {
  let bounds: Anchor<CGRect>
  let outlined: Bool
}
swift/Components/DropdownMenu.swift
import PhosphorSwift
import SwiftUI

enum MenuPresentation: Sendable {
  case system, popup
}

extension EnvironmentValues {
  @Entry var menuPresentation: MenuPresentation = .system
  @Entry var menuRadio = MenuRadioContext()
}

struct MenuRadioContext: @unchecked Sendable {
  var selected: AnyHashable?
  var select: @MainActor @Sendable (AnyHashable) -> Void = { _ in }
}
swift/Components/Field.swift
import SwiftUI

extension EnvironmentValues {
  @Entry public var isInvalid = false
  @Entry var fieldInset = false
  @Entry var fieldGroupInset = false
  @Entry var fieldSetInset = false
}

extension View {
  public func invalid(_ isInvalid: Bool = true) -> some View {
    environment(\.isInvalid, isInvalid)
  }
}

public enum FieldOrientation: CaseIterable, Sendable {
swift/Components/Item.swift
import SwiftUI

public enum ItemVariant: CaseIterable, Sendable {
  case `default`, outline, muted
}

public enum ItemSize: CaseIterable, Sendable {
  case `default`, sm, xs

  var horizontalPadding: CGFloat {
    switch self {
    case .default: 16
    case .sm: 14
    case .xs: 12
    }
  }
swift/Components/Overlay.swift
import PhosphorSwift
import Observation
import SwiftUI

public enum PopupAlignment: Sendable, CaseIterable {
  case start, center, end
}

struct OverlayDismiss: Sendable {
  let action: @MainActor @Sendable () -> Void

  @MainActor func callAsFunction() {
    action()
  }
}
swift/Components/Select.swift
import PhosphorSwift
import SwiftUI

struct SelectContext: @unchecked Sendable {
  var selected: AnyHashable?
  var registering = false
  var select: @MainActor @Sendable (AnyHashable) -> Void = { _ in }
}

extension EnvironmentValues {
  @Entry var select = SelectContext()
}

struct SelectLabelsKey: PreferenceKey {
  static var defaultValue: [AnyHashable: Text] { [:] }
swift/Components/Separator.swift
import SwiftUI

public struct Separator: View {
  let orientation: Axis

  public init(_ orientation: Axis = .horizontal) {
    self.orientation = orientation
  }

  public var body: some View {
    Rectangle()
      .fill(EsuissColor.border)
      .frame(width: orientation == .vertical ? 1 : nil, height: orientation == .horizontal ? 1 : nil)
      .frame(maxWidth: orientation == .horizontal ? .infinity : nil, maxHeight: orientation == .vertical ? .infinity : nil)
      .accessibilityHidden(true)
  }
swift/Components/TextVariant.swift
import SwiftUI

public enum TextVariant: CaseIterable, Sendable {
  case `default`, h1, h2, h3, h4, p, blockquote, code, lead, large, small, muted

  var style: TextStyle {
    switch self {
    case .h1: .xl3
    case .h2: .xl2
    case .h3: .xl
    case .h4, .lead, .large: .lg
    case .code, .small, .muted: .sm
    default: .base
    }
  }
  1. Add the files to your app target.
  1. Set minSdk to 24 or higher and add the dependencies:
build.gradle.kts
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
implementation("com.adamglin:phosphor-icon-android:1.0.0")
  1. Copy the theme files every view uses:
kotlin/theme/Theme.kt
package com.esuiss.ui.theme

import android.content.res.AssetManager
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
kotlin/theme/Tokens.kt
package com.esuiss.ui.theme

import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp

@Immutable
data class Colors(
    val background: Color,
    val foreground: Color,
    val card: Color,
    val cardForeground: Color,
    val popover: Color,
    val popoverForeground: Color,
    val primary: Color,
  1. Copy the source and every file it uses:
kotlin/components/Combobox.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
kotlin/components/Button.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
kotlin/components/ButtonGroup.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
kotlin/components/Checkbox.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.triStateToggleable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
kotlin/components/Content.kt
package com.esuiss.ui.components

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val LocalContentStyle = compositionLocalOf { TextStyle.Default }

val LocalContentColor = compositionLocalOf { Color.Unspecified }

val LocalIconSize = compositionLocalOf { 20.dp }
kotlin/components/DropdownMenu.kt
package com.esuiss.ui.components

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
kotlin/components/Field.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.LayoutScopeMarker
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
kotlin/components/FormSupport.kt
package com.esuiss.ui.components

import android.provider.Settings
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
kotlin/components/Haptics.kt
package com.esuiss.ui.components

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback

val LocalHapticsEnabled = staticCompositionLocalOf { true }

@Composable
fun ProvideHaptics(enabled: Boolean, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalHapticsEnabled provides enabled, content = content)
}
kotlin/components/Icon.kt
package com.esuiss.ui.components

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import com.esuiss.ui.theme.EsuissTheme

@Composable
fun Icon(
    imageVector: ImageVector,
kotlin/components/Label.kt
package com.esuiss.ui.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
kotlin/components/Overlay.kt
package com.esuiss.ui.components

import android.os.Build
import android.view.Window
import android.view.WindowManager
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.EnterExitState
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideIn
kotlin/components/Select.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
kotlin/components/Separator.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme

@Composable
fun Separator(
  1. Update the com.esuiss.ui package names to match your project.

Usage

import { ComboboxTrigger, ComboboxInput, ComboboxContent, ComboboxList, ComboboxItem, ComboboxGroup, ComboboxLabel, ComboboxEmpty, ComboboxSeparator, ComboboxChips, ComboboxChip, ComboboxChipsInput } from "@/components/ui/combobox"
<ComboboxDemo defaultValue="Expo" defaultOpen />

Pass items to the root and render the filtered list with a function child of ComboboxList. For grouped items, render a ComboboxGroup per group with a ComboboxLabel and a ComboboxCollection. Add ComboboxEmpty for the no-results message.

Use value, defaultValue and onValueChange for the selection, and inputValue with onInputValueChange for the typed text. For object items, provide itemToStringLabel. filter, limit and locale tune matching.

With chips, create a ref with useComboboxAnchor(), attach it to ComboboxChips, and pass it to ComboboxContent as anchor so the list lines up with the chips field. On ComboboxInput, set showClear to add a clear button and showTrigger={false} to hide the caret.

Examples

Default

Web

Web preview

import * as React from "react"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

function ComboboxDemo({
  defaultValue = null,
  defaultOpen,
import EsuissUI
import SwiftUI

let comboboxFrameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

struct ComboboxDemo: View {
  @State private var value: String?
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox

val ComboboxFrameworks = listOf("Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro", "Gatsby", "Expo")

@Composable

With value

Starts with a selected value.

Web

Web preview

import * as React from "react"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

function ComboboxDemo({
  defaultValue = null,
  defaultOpen,
import EsuissUI
import SwiftUI

struct ComboboxWithValueExample: View {
  var body: some View {
    ComboboxDemo(initial: "Expo")
  }
}

let comboboxFrameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox

@Composable
fun ComboboxWithValueExample() {
    ComboboxDemo(defaultValue = "Expo")

With clear

A clear button through showClear.

Web

Web preview

import * as React from "react"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

function ComboboxDemo({
  defaultValue = null,
  defaultOpen,
import EsuissUI
import SwiftUI

struct ComboboxWithClearExample: View {
  var body: some View {
    ComboboxDemo(initial: "Remix")
  }
}

let comboboxFrameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox

@Composable
fun ComboboxWithClearExample() {
    ComboboxDemo(defaultValue = "Remix", showClear = true)

Grouped

Items in labelled groups with separators.

Web

Web preview

import * as React from "react"
import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxList, ComboboxSeparator } from "@/components/ui/combobox"

interface Produce {
  value: string
  label: string
}

const produce: { value: string; items: Produce[] }[] = [
  {
    value: "Fruits",
    items: [
      { value: "mango", label: "Mango" },
      { value: "banana", label: "Banana" },
      { value: "blueberry", label: "Blueberry" },
    ],
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox

data class ComboboxProduce(val value: String, val label: String, val group: String)

val ComboboxProduceItems = listOf(
    ComboboxProduce("mango", "Mango", "Fruits"),
    ComboboxProduce("banana", "Banana", "Fruits"),

Multiple

Multiple selection shown as removable chips.

Web

Web preview

import * as React from "react"
import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxItem, ComboboxList, useComboboxAnchor } from "@/components/ui/combobox"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

function MultipleDemo() {
  const anchor = useComboboxAnchor()
  const [value, setValue] = React.useState<string[]>(["Expo", "Astro"])
import EsuissUI
import SwiftUI

let comboboxFrameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

struct ComboboxMultipleDemo: View {
  @State private var value = ["Expo", "Astro"]
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.MultiCombobox

val ComboboxFrameworks = listOf("Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro", "Gatsby", "Expo")

@Composable
fun ComboboxMultiple() {
    var values by rememberSaveable { mutableStateOf(listOf("Expo", "Astro")) }

With label

Linked to a Label with helper text.

Web

Web preview

import * as React from "react"
import { View } from "react-native"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"
import { Text } from "@/components/ui/text"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]
import EsuissUI
import SwiftUI

struct ComboboxWithLabelExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 8) {
      FieldLabel("Framework")
        .accessibilityHidden(true)
      ComboboxDemo()
        .accessibilityLabel(Text("Framework"))
      Text("Search and pick the framework you use.")
        .textStyle(.sm)
        .foregroundStyle(EsuissColor.mutedForeground)
    }
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox
import com.esuiss.ui.components.Label
import com.esuiss.ui.components.Text

Disabled

A disabled combobox.

Web

Web preview

import * as React from "react"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

function ComboboxDemo({
  defaultValue = null,
  defaultOpen,
import EsuissUI
import SwiftUI

struct ComboboxDisabledExample: View {
  var body: some View {
    ComboboxDemo(initial: "Astro")
      .disabled(true)
  }
}

let comboboxFrameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox

@Composable
fun ComboboxDisabledExample() {
    ComboboxDemo(defaultValue = "Astro", disabled = true)

Invalid

The invalid style with an error message.

Web

Web preview

import * as React from "react"
import { View } from "react-native"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"
import { Text } from "@/components/ui/text"

const frameworks = [
  "Next.js",
  "SvelteKit",
  "Nuxt.js",
  "Remix",
  "Astro",
  "Gatsby",
  "Expo",
]

function ComboboxDemo({
import EsuissUI
import SwiftUI

struct ComboboxInvalidExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 8) {
      ComboboxDemo()
        .invalid()
      Text("Please select a framework.")
        .textStyle(.sm)
        .foregroundStyle(EsuissColor.destructive)
    }
  }
}

let comboboxFrameworks = [
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Combobox
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme

API Reference

01

Combobox

The root that holds the items, the selected value, the typed text and the open state.

PropTypeDefaultDescription
itemsItem[] | ComboboxItemGroup<Item>[]-The items to filter, flat or grouped.
valueValue | Value[] | null-The selected value, for controlled use.
defaultValueValue | Value[] | null-The initial value when uncontrolled.
onValueChange(value, details) => void-Called when the selection changes.
multiplebooleanfalseAllows several values to be selected.
inputValuestring-The typed text, for controlled use.
onInputValueChange(inputValue: string, details) => void-Called when the typed text changes.
filterComboboxFilter<Item> | null-Custom filter function; null turns filtering off.
limitnumber-The maximum number of items shown.
itemToStringLabel(itemValue: Value) => string-Returns the label shown for an item value.
openOnInputClickbooleantrueOpens the list when the input is pressed.
openboolean-Whether the list is open, for controlled use.
defaultOpenbooleanfalseWhether the list starts open when uncontrolled.
onOpenChange(open: boolean, details) => void-Called when the list opens or closes.
disabledbooleanfalseDisables the combobox.
readOnlybooleanfalseShows the value without allowing changes.
02

ComboboxTrigger

Round button that opens the list.

PropTypeDefaultDescription
childrenReact.ReactNode-

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Trigger>.

03

ComboboxInput

Text field with caret trigger and optional clear button.

PropTypeDefaultDescription
classNamestring-
childrenReact.ReactNode-
showTriggerbooleantrueShows the caret button. Defaults to true.
showClearbooleanfalseShows a clear button when there is text or a value.
invalidboolean-Shows the destructive border; also read from a surrounding Field.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Input>.

04

ComboboxContent

Floating popup for the list.

PropTypeDefaultDescription
portalHoststring-Name of the portal host to render into.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Popup>, PositionerProps.

05

ComboboxList

Scrollable list of items.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.List>.

06

ComboboxItem

Selectable option with a check mark when selected.

PropTypeDefaultDescription
childrenReact.ReactNode-

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Item>.

07

ComboboxGroup

Group of items.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Group>.

08

ComboboxLabel

Heading for a group.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.GroupLabel>.

09

ComboboxEmpty

Message shown when nothing matches.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Empty>.

10

ComboboxSeparator

Line between groups.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Separator>.

11

ComboboxChips

Field that holds chips for multiple selection.

PropTypeDefaultDescription
invalidboolean-Shows the destructive border.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Chips>.

12

ComboboxChip

Selected value with a remove button.

PropTypeDefaultDescription
showRemovebooleantrueShows the remove button. Defaults to true.
removeLabelstringRemove {value}Accessible name of the remove button. Defaults to Remove followed by the chip text.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Chip>.

13

ComboboxChipsInput

Text input inside the chips field.

Also accepts the props of React.ComponentProps<typeof ComboboxPrimitive.Input>.

Accessibility

  • The input has the combobox role with expanded and disabled state.
  • Items have the option role with selected and disabled state; the list has the list role.
  • The empty message is a polite live region.
  • The trigger, clear and chip remove buttons are labelled "Show options", "Clear" and "Remove".

Notes

  • Uses bg-secondary, bg-popover, border-ring and border-destructive tokens; light and dark follow the system appearance.
  • In Uniwind and NativeWind, a focused field also draws the ring-3 ring-ring/50 focus ring.
  • Fade animations respect the system Reduce Motion setting.
NextCommandPreviousCollapsible

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • With clear button
    • Disabled
    • Grouped
    • Highlight match
    • Multiple
  4. 04API Reference
    • Combobox
    • ComboboxValue
    • ComboboxTrigger
    • ComboboxInput
    • ComboboxContent
    • ComboboxList
    • ComboboxItem
    • ComboboxGroup
    • ComboboxLabel
    • ComboboxCollection
    • ComboboxEmpty
    • ComboboxSeparator
    • ComboboxChips
    • ComboboxChip
    • ComboboxChipsInput
  5. 05Accessibility
  6. 06Notes
  7. 07Installation
    • Manual
  8. 08Usage
  9. 09Examples
    • Default
    • With value
    • With clear
    • Grouped
    • Multiple
    • With label
    • Disabled
    • Invalid
  10. 10API Reference
    • Combobox
    • ComboboxTrigger
    • ComboboxInput
    • ComboboxContent
    • ComboboxList
    • ComboboxItem
    • ComboboxGroup
    • ComboboxLabel
    • ComboboxEmpty
    • ComboboxSeparator
    • ComboboxChips
    • ComboboxChip
    • ComboboxChipsInput
  11. 11Accessibility
  12. 12Notes