suiss/uim · React Native Component
Select
A control that opens a floating list of options and shows the chosen one in its trigger.
Select is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.


import * as React from "react"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
function SelectDemo({
initial,
size,
disabled = false,
invalid = false,
labelledBy,
defaultOpen,
}: {
initial?: Option
size?: "default" | "sm"
disabled?: boolean
invalid?: boolean
labelledBy?: string
defaultOpen?: boolean
}) {
const [value, setValue] = React.useState<string | null>(
initial?.value ?? null
)
return (
<Select
items={fruits}
value={value}
onValueChange={setValue}
disabled={disabled}
defaultOpen={defaultOpen}
>
<SelectTrigger
size={size}
accessibilityLabelledBy={labelledBy}
invalid={invalid}
className="w-48"
>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent className="w-48">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectOpenByDefaultExample() {
return (
<SelectDemo initial={fruits[2]} defaultOpen />
)
}Select is built on the select primitive. SelectTrigger is a pressable field with the combobox role and an up-down caret. SelectContent renders in a portal, positioned next to the trigger, and fades in and out with Reanimated. The list matches the trigger width.
The trigger comes in two sizes, default and sm. Use Select for picking one value from a short list; for a list that needs searching, use Combobox.
Installation
$ pnpm dlx @esuiss/uim@latest add select$ pnpm dlx @esuiss/uim@latest add select --platform swift$ pnpm dlx @esuiss/uim@latest add select --platform kotlinThe CLI also adds what it builds on: Icon, Text.
Manual
- Install the dependencies:
npx expo install cn phosphor-react-native react-native-reanimated react-native-screens react-native-svg react-native-worklets-
Add the pieces it builds on: Icon, Text, and the primitives
components/primitives/field.tsx,components/primitives/positioner.tsx,components/primitives/select.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/select.tsx:
import { cn } from "cn"
import { CaretUpDownIcon, CheckIcon } from "phosphor-react-native"
import * as React from "react"
import { Platform, type StyleProp, type ViewStyle } from "react-native"
import { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import { useFieldRootContext } from "@/components/primitives/field"
import { useOptionalPositionerContext } from "@/components/primitives/positioner"
import * as SelectPrimitive from "@/components/primitives/select"
import {
resolveClassName,
resolveStyle,
} from "@/components/primitives/use-render"
import { AnimatedView } from "@/components/ui/animated-view"
import { Icon } from "@/components/ui/icon"
import { TextClassContext } from "@/components/ui/text"- Update the import paths to match your project.
- Install the dependencies:
npx expo install cn phosphor-react-native react-native-reanimated react-native-screens react-native-svg react-native-worklets-
Add the pieces it builds on: Icon, Text, and the primitives
components/primitives/field.tsx,components/primitives/positioner.tsx,components/primitives/select.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/select.tsx:
import { cn } from "cn"
import { CaretUpDownIcon, CheckIcon } from "phosphor-react-native"
import * as React from "react"
import { Platform, type StyleProp, type ViewStyle } from "react-native"
import { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import { useFieldRootContext } from "@/components/primitives/field"
import { useOptionalPositionerContext } from "@/components/primitives/positioner"
import * as SelectPrimitive from "@/components/primitives/select"
import {
resolveClassName,
resolveStyle,
} from "@/components/primitives/use-render"
import { AnimatedView } from "@/components/ui/animated-view"
import { Icon } from "@/components/ui/icon"
import { TextClassContext } from "@/components/ui/text"- Update the import paths to match your project.
- Install the dependencies:
npx expo install phosphor-react-native react-native-reanimated react-native-screens react-native-svg react-native-worklets-
Add the pieces it builds on: Icon, Text, and the primitives
components/primitives/field.tsx,components/primitives/positioner.tsx,components/primitives/select.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/select.tsx:
import { CaretUpDownIcon, CheckIcon } from "phosphor-react-native"
import * as React from "react"
import {
Platform,
type StyleProp,
StyleSheet,
type TextStyle,
type ViewStyle,
} from "react-native"
import { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import { useFieldRootContext } from "@/components/primitives/field"
import { useOptionalPositionerContext } from "@/components/primitives/positioner"
import * as SelectPrimitive from "@/components/primitives/select"
import { resolveStyle } from "@/components/primitives/use-render"
import { AnimatedView } from "@/components/ui/animated-view"- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Add the Swift package to your app:
.package(url: "https://github.com/phosphor-icons/swift", from: "2.1.0")- Copy the theme files every view uses:
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
}import SwiftUI
extension Color {
public static let surface = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
public static let surfaceSecondary = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
public static let surfaceTertiary = Color.adaptive(light: .init(0xE8E8ED), dark: .init(0x2C2C2E))
public static let surfaceRaised = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
public static let label = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let labelSecondary = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
public static let labelTertiary = Color.adaptive(light: .init(0x86868B), dark: .init(0x86868B))
public static let labelQuaternary = Color.adaptive(light: .init(0xD2D2D7), dark: .init(0x424245))
public static let control = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
public static let controlPressed = Color.adaptive(light: .init(0x000000, 0.12), dark: .init(0xFFFFFF, 0.22))
public static let controlSelected = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let controlDisabled = Color.adaptive(light: .init(0x000000, 0.03), dark: .init(0xFFFFFF, 0.05))
public static let onControlSelected = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))- Copy the source and every file it uses:
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] { [:] }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: 24
case .sm, .iconSm: 32
case .default, .icon: 36
case .lg, .iconLg: 44import 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
}import PhosphorSwift
import SwiftUI
extension Shadow {
static let control = Shadow(color: .black.opacity(0.08), radius: 0.5, y: 0.5)
static let thumb = Shadow(color: .black.opacity(0.12), radius: 1.5, y: 1)
}
struct SelectionIndicator<Mark: View>: View {
let checked: Bool
let pressed: Bool
let cornerRadius: CGFloat
let mark: Mark
@Environment(\.isEnabled) private var isEnabled
@Environment(\.isInvalid) private var invalidimport 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 }
}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 {import SwiftUI
extension EnvironmentValues {
@Entry public var hapticsEnabled = true
}
extension View {
public func haptics(_ enabled: Bool) -> some View {
environment(\.hapticsEnabled, enabled)
}
func hapticFeedback<Trigger: Equatable>(_ feedback: SensoryFeedback, trigger: Trigger) -> some View {
modifier(HapticFeedbackModifier(feedback: feedback, trigger: trigger) { _, _ in true })
}
func hapticFeedback<Trigger: Equatable>(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()
}
}import SwiftUI
public struct Separator: View {
let orientation: Axis
public init(_ orientation: Axis = .horizontal) {
self.orientation = orientation
}
public var body: some View {
Rectangle()
.fill(Color.separator)
.frame(width: orientation == .vertical ? 1 : nil, height: orientation == .horizontal ? 1 : nil)
.frame(maxWidth: orientation == .horizontal ? .infinity : nil, maxHeight: orientation == .vertical ? .infinity : nil)
.accessibilityHidden(true)
}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
}
}- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
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")- Copy the theme files every view uses:
package com.esuiss.ui.theme
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.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffsetpackage com.esuiss.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Immutable
data class Colors(
val surface: Color,
val surfaceSecondary: Color,
val surfaceTertiary: Color,
val surfaceRaised: Color,
val label: Color,
val labelSecondary: Color,
val labelTertiary: Color,
val labelQuaternary: Color,- Copy the source and every file it uses:
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.heightInpackage 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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShapepackage 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.Immutablepackage 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 }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.BasicTextpackage 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.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Sizepackage 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,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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
@Composablepackage 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.slideInpackage 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(- Update the
com.esuiss.uipackage names to match your project.
Usage
import { SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator } from "@/components/ui/select"<SelectDemo initial={fruits[2]} defaultOpen />Put a SelectValue inside SelectTrigger, and SelectItem entries inside SelectContent. Group items with SelectGroup and SelectLabel, and divide groups with SelectSeparator.
Control the value with value and onValueChange on Select, or start from defaultValue. Pass the full option list as items so SelectValue can show the label of the current value. Select also accepts multiple, disabled, readOnly, open, defaultOpen and onOpenChange.
Give each SelectItem a value and a label; the label is shown when no children are passed. SelectValue shows placeholder until a value is chosen. The Option type ({ value, label }) is also exported.
Examples
Default


import * as React from "react"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
function SelectDemo({
initial,
size,
disabled = false,
invalid = false,
labelledBy,
defaultOpen,
}: {
initial?: Option
size?: "default" | "sm"
disabled?: boolean
invalid?: boolean
labelledBy?: string
defaultOpen?: boolean
}) {
const [value, setValue] = React.useState<string | null>(
initial?.value ?? null
)
return (
<Select
items={fruits}
value={value}
onValueChange={setValue}
disabled={disabled}
defaultOpen={defaultOpen}
>
<SelectTrigger
size={size}
accessibilityLabelledBy={labelledBy}
invalid={invalid}
className="w-48"
>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent className="w-48">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectExample() {
return (
<SelectDemo />
)
}import EsuissUI
import SwiftUI
struct SelectOption {
let value: String
let label: LocalizedStringKey
}
let selectFruits = [
SelectOption(value: "mango", label: "Mango"),
SelectOption(value: "banana", label: "Banana"),
SelectOption(value: "blueberry", label: "Blueberry"),
SelectOption(value: "grapes", label: "Grapes"),
SelectOption(value: "pineapple", label: "Pineapple"),
]
struct SelectDemo: View {
var size: SelectSize = .default
@State private var value: String?
init(initial: String? = nil, size: SelectSize = .default) {
self.size = size
_value = State(initialValue: initial)
}
var body: some View {
SelectField(selection: $value, placeholder: "Select a fruit", size: size) {
SelectGroup {
SelectLabel("Fruits")
ForEach(selectFruits, id: \.value) { fruit in
SelectItem(fruit.label, value: fruit.value)
}
}
}
.frame(width: 192)
}
}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.Select
import com.esuiss.ui.components.SelectGroup
import com.esuiss.ui.components.SelectItem
import com.esuiss.ui.components.SelectLabel
import com.esuiss.ui.components.SelectOption
import com.esuiss.ui.components.SelectSize
val selectFruits = listOf(
SelectOption("mango", "Mango"),
SelectOption("banana", "Banana"),
SelectOption("blueberry", "Blueberry"),
SelectOption("grapes", "Grapes"),
SelectOption("pineapple", "Pineapple"),
)
@Composable
fun SelectDemo(
initial: String? = null,
size: SelectSize = SelectSize.Default,
enabled: Boolean = true,
invalid: Boolean = false,
label: String? = null,
) {
var value by rememberSaveable { mutableStateOf(initial) }
Select(
value = value,
onValueChange = { value = it },
displayValue = selectFruits.firstOrNull { it.value == value }?.label,
modifier = Modifier
.width(192.dp)
.then(if (label != null) Modifier.semantics { contentDescription = label } else Modifier),
placeholder = "Select a fruit",
size = size,
invalid = invalid,
enabled = enabled,
) {
SelectGroup {
SelectLabel("Fruits")
selectFruits.forEach { SelectItem(it.value, it.label) }
}
}
}With value
A select that starts with a chosen value.


import * as React from "react"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
function SelectDemo({
initial,
size,
disabled = false,
invalid = false,
labelledBy,
defaultOpen,
}: {
initial?: Option
size?: "default" | "sm"
disabled?: boolean
invalid?: boolean
labelledBy?: string
defaultOpen?: boolean
}) {
const [value, setValue] = React.useState<string | null>(
initial?.value ?? null
)
return (
<Select
items={fruits}
value={value}
onValueChange={setValue}
disabled={disabled}
defaultOpen={defaultOpen}
>
<SelectTrigger
size={size}
accessibilityLabelledBy={labelledBy}
invalid={invalid}
className="w-48"
>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent className="w-48">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectWithValueExample() {
return (
<SelectDemo initial={fruits[2]} />
)
}import EsuissUI
import SwiftUI
struct SelectWithValueExample: View {
var body: some View {
SelectDemo(initial: "blueberry")
}
}
struct SelectOption {
let value: String
let label: LocalizedStringKey
}
let selectFruits = [
SelectOption(value: "mango", label: "Mango"),
SelectOption(value: "banana", label: "Banana"),
SelectOption(value: "blueberry", label: "Blueberry"),
SelectOption(value: "grapes", label: "Grapes"),
SelectOption(value: "pineapple", label: "Pineapple"),
]
struct SelectDemo: View {
var size: SelectSize = .default
@State private var value: String?
init(initial: String? = nil, size: SelectSize = .default) {
self.size = size
_value = State(initialValue: initial)
}
var body: some View {
SelectField(selection: $value, placeholder: "Select a fruit", size: size) {
SelectGroup {
SelectLabel("Fruits")
ForEach(selectFruits, id: \.value) { fruit in
SelectItem(fruit.label, value: fruit.value)
}
}
}
.frame(width: 192)
}
}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.Select
import com.esuiss.ui.components.SelectGroup
import com.esuiss.ui.components.SelectItem
import com.esuiss.ui.components.SelectLabel
import com.esuiss.ui.components.SelectOption
import com.esuiss.ui.components.SelectSize
@Composable
fun SelectWithValueExample() {
SelectDemo(initial = "blueberry")
}
val selectFruits = listOf(
SelectOption("mango", "Mango"),
SelectOption("banana", "Banana"),
SelectOption("blueberry", "Blueberry"),
SelectOption("grapes", "Grapes"),
SelectOption("pineapple", "Pineapple"),
)
@Composable
fun SelectDemo(
initial: String? = null,
size: SelectSize = SelectSize.Default,
enabled: Boolean = true,
invalid: Boolean = false,
label: String? = null,
) {
var value by rememberSaveable { mutableStateOf(initial) }
Select(
value = value,
onValueChange = { value = it },
displayValue = selectFruits.firstOrNull { it.value == value }?.label,
modifier = Modifier
.width(192.dp)
.then(if (label != null) Modifier.semantics { contentDescription = label } else Modifier),
placeholder = "Select a fruit",
size = size,
invalid = invalid,
enabled = enabled,
) {
SelectGroup {
SelectLabel("Fruits")
selectFruits.forEach { SelectItem(it.value, it.label) }
}
}
}Small
The sm trigger size.


import * as React from "react"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
function SelectDemo({
initial,
size,
disabled = false,
invalid = false,
labelledBy,
defaultOpen,
}: {
initial?: Option
size?: "default" | "sm"
disabled?: boolean
invalid?: boolean
labelledBy?: string
defaultOpen?: boolean
}) {
const [value, setValue] = React.useState<string | null>(
initial?.value ?? null
)
return (
<Select
items={fruits}
value={value}
onValueChange={setValue}
disabled={disabled}
defaultOpen={defaultOpen}
>
<SelectTrigger
size={size}
accessibilityLabelledBy={labelledBy}
invalid={invalid}
className="w-48"
>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent className="w-48">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectSmallExample() {
return (
<SelectDemo size="sm" />
)
}import EsuissUI
import SwiftUI
struct SelectSmallExample: View {
var body: some View {
SelectDemo(size: .sm)
}
}
struct SelectOption {
let value: String
let label: LocalizedStringKey
}
let selectFruits = [
SelectOption(value: "mango", label: "Mango"),
SelectOption(value: "banana", label: "Banana"),
SelectOption(value: "blueberry", label: "Blueberry"),
SelectOption(value: "grapes", label: "Grapes"),
SelectOption(value: "pineapple", label: "Pineapple"),
]
struct SelectDemo: View {
var size: SelectSize = .default
@State private var value: String?
init(initial: String? = nil, size: SelectSize = .default) {
self.size = size
_value = State(initialValue: initial)
}
var body: some View {
SelectField(selection: $value, placeholder: "Select a fruit", size: size) {
SelectGroup {
SelectLabel("Fruits")
ForEach(selectFruits, id: \.value) { fruit in
SelectItem(fruit.label, value: fruit.value)
}
}
}
.frame(width: 192)
}
}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.Select
import com.esuiss.ui.components.SelectGroup
import com.esuiss.ui.components.SelectItem
import com.esuiss.ui.components.SelectLabel
import com.esuiss.ui.components.SelectOption
import com.esuiss.ui.components.SelectSize
@Composable
fun SelectSmallExample() {
SelectDemo(size = SelectSize.Sm)
}
val selectFruits = listOf(
SelectOption("mango", "Mango"),
SelectOption("banana", "Banana"),
SelectOption("blueberry", "Blueberry"),
SelectOption("grapes", "Grapes"),
SelectOption("pineapple", "Pineapple"),
)
@Composable
fun SelectDemo(
initial: String? = null,
size: SelectSize = SelectSize.Default,
enabled: Boolean = true,
invalid: Boolean = false,
label: String? = null,
) {
var value by rememberSaveable { mutableStateOf(initial) }
Select(
value = value,
onValueChange = { value = it },
displayValue = selectFruits.firstOrNull { it.value == value }?.label,
modifier = Modifier
.width(192.dp)
.then(if (label != null) Modifier.semantics { contentDescription = label } else Modifier),
placeholder = "Select a fruit",
size = size,
invalid = invalid,
enabled = enabled,
) {
SelectGroup {
SelectLabel("Fruits")
selectFruits.forEach { SelectItem(it.value, it.label) }
}
}
}Grouped
Two labeled groups divided by a separator.


import * as React from "react"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } from "@/components/ui/select"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
const vegetables: Option[] = [
{ value: "carrot", label: "Carrot" },
{ value: "broccoli", label: "Broccoli" },
{ value: "spinach", label: "Spinach" },
]
function GroupedDemo() {
const [value, setValue] = React.useState<string | null>(null)
return (
<Select
items={[...fruits.slice(0, 3), ...vegetables]}
value={value}
onValueChange={setValue}
>
<SelectTrigger className="w-56">
<SelectValue placeholder="Select produce" />
</SelectTrigger>
<SelectContent className="w-56">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.slice(0, 3).map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Vegetables</SelectLabel>
{vegetables.map((vegetable) => (
<SelectItem
key={vegetable.value}
value={vegetable.value}
label={vegetable.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectGroupedExample() {
return (
<GroupedDemo />
)
}import EsuissUI
import SwiftUI
struct SelectOption {
let value: String
let label: LocalizedStringKey
}
let selectFruits = [
SelectOption(value: "mango", label: "Mango"),
SelectOption(value: "banana", label: "Banana"),
SelectOption(value: "blueberry", label: "Blueberry"),
SelectOption(value: "grapes", label: "Grapes"),
SelectOption(value: "pineapple", label: "Pineapple"),
]
let selectVegetables = [
SelectOption(value: "carrot", label: "Carrot"),
SelectOption(value: "broccoli", label: "Broccoli"),
SelectOption(value: "spinach", label: "Spinach"),
]
struct SelectGroupedDemo: View {
@State private var value: String?
var body: some View {
SelectField(selection: $value, placeholder: "Select produce") {
SelectGroup {
SelectLabel("Fruits")
ForEach(selectFruits.prefix(3), id: \.value) { fruit in
SelectItem(fruit.label, value: fruit.value)
}
}
SelectSeparator()
SelectGroup {
SelectLabel("Vegetables")
ForEach(selectVegetables, id: \.value) { vegetable in
SelectItem(vegetable.label, value: vegetable.value)
}
}
}
.frame(width: 224)
}
}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.Select
import com.esuiss.ui.components.SelectGroup
import com.esuiss.ui.components.SelectItem
import com.esuiss.ui.components.SelectLabel
import com.esuiss.ui.components.SelectOption
import com.esuiss.ui.components.SelectSeparator
val selectFruits = listOf(
SelectOption("mango", "Mango"),
SelectOption("banana", "Banana"),
SelectOption("blueberry", "Blueberry"),
SelectOption("grapes", "Grapes"),
SelectOption("pineapple", "Pineapple"),
)
val selectVegetables = listOf(
SelectOption("carrot", "Carrot"),
SelectOption("broccoli", "Broccoli"),
SelectOption("spinach", "Spinach"),
)
@Composable
fun SelectGrouped() {
var value by rememberSaveable { mutableStateOf<String?>(null) }
val fruits = selectFruits.take(3)
Select(
value = value,
onValueChange = { value = it },
displayValue = (fruits + selectVegetables).firstOrNull { it.value == value }?.label,
modifier = Modifier.width(224.dp),
placeholder = "Select produce",
) {
SelectGroup {
SelectLabel("Fruits")
fruits.forEach { SelectItem(it.value, it.label) }
}
SelectSeparator()
SelectGroup {
SelectLabel("Vegetables")
selectVegetables.forEach { SelectItem(it.value, it.label) }
}
}
}With label
A select labeled by a Label through accessibilityLabelledBy, with helper text.


import * as React from "react"
import { View } from "react-native"
import { Label } from "@/components/ui/label"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Text } from "@/components/ui/text"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
function SelectDemo({
initial,
size,
disabled = false,
invalid = false,
labelledBy,
defaultOpen,
}: {
initial?: Option
size?: "default" | "sm"
disabled?: boolean
invalid?: boolean
labelledBy?: string
defaultOpen?: boolean
}) {
const [value, setValue] = React.useState<string | null>(
initial?.value ?? null
)
return (
<Select
items={fruits}
value={value}
onValueChange={setValue}
disabled={disabled}
defaultOpen={defaultOpen}
>
<SelectTrigger
size={size}
accessibilityLabelledBy={labelledBy}
invalid={invalid}
className="w-48"
>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent className="w-48">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectWithLabelExample() {
return (
<View className="gap-2">
<Label nativeID="select-fruit">Favorite fruit</Label>
<SelectDemo labelledBy="select-fruit" />
<Text className="text-sm text-label-secondary">
Used to personalize your recommendations.
</Text>
</View>
)
}import EsuissUI
import SwiftUI
struct SelectWithLabelExample: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
FieldLabel("Favorite fruit")
.accessibilityHidden(true)
SelectDemo()
.accessibilityLabel(Text("Favorite fruit"))
Text("Used to personalize your recommendations.")
.textStyle(.sm)
.foregroundStyle(Color.labelSecondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
struct SelectOption {
let value: String
let label: LocalizedStringKey
}
let selectFruits = [
SelectOption(value: "mango", label: "Mango"),
SelectOption(value: "banana", label: "Banana"),
SelectOption(value: "blueberry", label: "Blueberry"),
SelectOption(value: "grapes", label: "Grapes"),
SelectOption(value: "pineapple", label: "Pineapple"),
]
struct SelectDemo: View {
var size: SelectSize = .default
@State private var value: String?
init(initial: String? = nil, size: SelectSize = .default) {
self.size = size
_value = State(initialValue: initial)
}
var body: some View {
SelectField(selection: $value, placeholder: "Select a fruit", size: size) {
SelectGroup {
SelectLabel("Fruits")
ForEach(selectFruits, id: \.value) { fruit in
SelectItem(fruit.label, value: fruit.value)
}
}
}
.frame(width: 192)
}
}import androidx.compose.foundation.layout.Arrangement
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.Label
import com.esuiss.ui.components.Select
import com.esuiss.ui.components.SelectGroup
import com.esuiss.ui.components.SelectItem
import com.esuiss.ui.components.SelectLabel
import com.esuiss.ui.components.SelectOption
import com.esuiss.ui.components.SelectSize
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
val selectFruits = listOf(
SelectOption("mango", "Mango"),
SelectOption("banana", "Banana"),
SelectOption("blueberry", "Blueberry"),
SelectOption("grapes", "Grapes"),
SelectOption("pineapple", "Pineapple"),
)
@Composable
fun SelectDemo(
initial: String? = null,
size: SelectSize = SelectSize.Default,
enabled: Boolean = true,
invalid: Boolean = false,
label: String? = null,
) {
var value by rememberSaveable { mutableStateOf(initial) }
Select(
value = value,
onValueChange = { value = it },
displayValue = selectFruits.firstOrNull { it.value == value }?.label,
modifier = Modifier
.width(192.dp)
.then(if (label != null) Modifier.semantics { contentDescription = label } else Modifier),
placeholder = "Select a fruit",
size = size,
invalid = invalid,
enabled = enabled,
) {
SelectGroup {
SelectLabel("Fruits")
selectFruits.forEach { SelectItem(it.value, it.label) }
}
}
}
@Composable
fun SelectWithLabel() {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Label("Favorite fruit")
SelectDemo(label = "Favorite fruit")
Text("Used to personalize your recommendations.", style = Typography.sm, color = EsuissTheme.colors.labelSecondary)
}
}Disabled
A disabled select with a value.


import * as React from "react"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
function SelectDemo({
initial,
size,
disabled = false,
invalid = false,
labelledBy,
defaultOpen,
}: {
initial?: Option
size?: "default" | "sm"
disabled?: boolean
invalid?: boolean
labelledBy?: string
defaultOpen?: boolean
}) {
const [value, setValue] = React.useState<string | null>(
initial?.value ?? null
)
return (
<Select
items={fruits}
value={value}
onValueChange={setValue}
disabled={disabled}
defaultOpen={defaultOpen}
>
<SelectTrigger
size={size}
accessibilityLabelledBy={labelledBy}
invalid={invalid}
className="w-48"
>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent className="w-48">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectDisabledExample() {
return (
<SelectDemo disabled initial={fruits[0]} />
)
}import EsuissUI
import SwiftUI
struct SelectDisabledExample: View {
var body: some View {
SelectDemo(initial: "mango")
.disabled(true)
}
}
struct SelectOption {
let value: String
let label: LocalizedStringKey
}
let selectFruits = [
SelectOption(value: "mango", label: "Mango"),
SelectOption(value: "banana", label: "Banana"),
SelectOption(value: "blueberry", label: "Blueberry"),
SelectOption(value: "grapes", label: "Grapes"),
SelectOption(value: "pineapple", label: "Pineapple"),
]
struct SelectDemo: View {
var size: SelectSize = .default
@State private var value: String?
init(initial: String? = nil, size: SelectSize = .default) {
self.size = size
_value = State(initialValue: initial)
}
var body: some View {
SelectField(selection: $value, placeholder: "Select a fruit", size: size) {
SelectGroup {
SelectLabel("Fruits")
ForEach(selectFruits, id: \.value) { fruit in
SelectItem(fruit.label, value: fruit.value)
}
}
}
.frame(width: 192)
}
}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.Select
import com.esuiss.ui.components.SelectGroup
import com.esuiss.ui.components.SelectItem
import com.esuiss.ui.components.SelectLabel
import com.esuiss.ui.components.SelectOption
import com.esuiss.ui.components.SelectSize
@Composable
fun SelectDisabledExample() {
SelectDemo(initial = "mango", enabled = false)
}
val selectFruits = listOf(
SelectOption("mango", "Mango"),
SelectOption("banana", "Banana"),
SelectOption("blueberry", "Blueberry"),
SelectOption("grapes", "Grapes"),
SelectOption("pineapple", "Pineapple"),
)
@Composable
fun SelectDemo(
initial: String? = null,
size: SelectSize = SelectSize.Default,
enabled: Boolean = true,
invalid: Boolean = false,
label: String? = null,
) {
var value by rememberSaveable { mutableStateOf(initial) }
Select(
value = value,
onValueChange = { value = it },
displayValue = selectFruits.firstOrNull { it.value == value }?.label,
modifier = Modifier
.width(192.dp)
.then(if (label != null) Modifier.semantics { contentDescription = label } else Modifier),
placeholder = "Select a fruit",
size = size,
invalid = invalid,
enabled = enabled,
) {
SelectGroup {
SelectLabel("Fruits")
selectFruits.forEach { SelectItem(it.value, it.label) }
}
}
}Invalid
The invalid state with an error message.


import * as React from "react"
import { View } from "react-native"
import { type Option, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Text } from "@/components/ui/text"
const fruits: Option[] = [
{ value: "mango", label: "Mango" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" },
{ value: "grapes", label: "Grapes" },
{ value: "pineapple", label: "Pineapple" },
]
function SelectDemo({
initial,
size,
disabled = false,
invalid = false,
labelledBy,
defaultOpen,
}: {
initial?: Option
size?: "default" | "sm"
disabled?: boolean
invalid?: boolean
labelledBy?: string
defaultOpen?: boolean
}) {
const [value, setValue] = React.useState<string | null>(
initial?.value ?? null
)
return (
<Select
items={fruits}
value={value}
onValueChange={setValue}
disabled={disabled}
defaultOpen={defaultOpen}
>
<SelectTrigger
size={size}
accessibilityLabelledBy={labelledBy}
invalid={invalid}
className="w-48"
>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent className="w-48">
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((fruit) => (
<SelectItem
key={fruit.value}
value={fruit.value}
label={fruit.label}
/>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
export function SelectInvalidExample() {
return (
<View className="gap-2">
<SelectDemo invalid />
<Text className="text-sm text-danger">Please select a fruit.</Text>
</View>
)
}import EsuissUI
import SwiftUI
struct SelectInvalidExample: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
SelectDemo()
.invalid()
Text("Please select a fruit.")
.textStyle(.sm)
.foregroundStyle(Color.danger)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
struct SelectOption {
let value: String
let label: LocalizedStringKey
}
let selectFruits = [
SelectOption(value: "mango", label: "Mango"),
SelectOption(value: "banana", label: "Banana"),
SelectOption(value: "blueberry", label: "Blueberry"),
SelectOption(value: "grapes", label: "Grapes"),
SelectOption(value: "pineapple", label: "Pineapple"),
]
struct SelectDemo: View {
var size: SelectSize = .default
@State private var value: String?
init(initial: String? = nil, size: SelectSize = .default) {
self.size = size
_value = State(initialValue: initial)
}
var body: some View {
SelectField(selection: $value, placeholder: "Select a fruit", size: size) {
SelectGroup {
SelectLabel("Fruits")
ForEach(selectFruits, id: \.value) { fruit in
SelectItem(fruit.label, value: fruit.value)
}
}
}
.frame(width: 192)
}
}import androidx.compose.foundation.layout.Arrangement
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.Select
import com.esuiss.ui.components.SelectGroup
import com.esuiss.ui.components.SelectItem
import com.esuiss.ui.components.SelectLabel
import com.esuiss.ui.components.SelectOption
import com.esuiss.ui.components.SelectSize
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
val selectFruits = listOf(
SelectOption("mango", "Mango"),
SelectOption("banana", "Banana"),
SelectOption("blueberry", "Blueberry"),
SelectOption("grapes", "Grapes"),
SelectOption("pineapple", "Pineapple"),
)
@Composable
fun SelectDemo(
initial: String? = null,
size: SelectSize = SelectSize.Default,
enabled: Boolean = true,
invalid: Boolean = false,
label: String? = null,
) {
var value by rememberSaveable { mutableStateOf(initial) }
Select(
value = value,
onValueChange = { value = it },
displayValue = selectFruits.firstOrNull { it.value == value }?.label,
modifier = Modifier
.width(192.dp)
.then(if (label != null) Modifier.semantics { contentDescription = label } else Modifier),
placeholder = "Select a fruit",
size = size,
invalid = invalid,
enabled = enabled,
) {
SelectGroup {
SelectLabel("Fruits")
selectFruits.forEach { SelectItem(it.value, it.label) }
}
}
}
@Composable
fun SelectInvalid() {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
SelectDemo(invalid = true)
Text("Please select a fruit.", style = Typography.sm, color = EsuissTheme.colors.danger)
}
}API Reference
SelectGroup
Groups related items.
React.ComponentProps<typeof SelectPrimitive.Group>SelectValue
Shows the selected label or the placeholder.
React.ComponentProps<typeof SelectPrimitive.Value>SelectTrigger
Pressable field that opens the list.
React.ComponentProps<typeof SelectPrimitive.Trigger>- Type
React.ReactNode- Default
- —
- About
- Trigger height:
defaultorsm. - Type
"default""sm"- Default
- "default"
- About
- Shows the danger border. Falls back to the invalid state of an enclosing field.
- Type
boolean- Default
- —
SelectContent
Floating list rendered in a portal.
React.ComponentProps<typeof SelectPrimitive.Popup>PositionerProps- About
- Name of the portal host to render into.
- Type
string- Default
- —
SelectLabel
Heading for a group.
React.ComponentProps<typeof SelectPrimitive.GroupLabel>SelectItem
One option, with a check mark when selected.
React.ComponentProps<typeof SelectPrimitive.Item>- Type
React.ReactNode- Default
- —
SelectSeparator
Line between groups.
React.ComponentProps<typeof SelectPrimitive.Separator>Accessibility
- The trigger has the
comboboxrole and reports its expanded and disabled states. - The list has the
listrole; each item has theoptionrole, uses its label asaccessibilityLabeland reports selected and disabled states. - Groups have the
grouprole and are labeled by theirSelectLabel. - Label the trigger with
accessibilityLabelledBypointing at aLabel'snativeID, or withaccessibilityLabel. - The Android back button and the VoiceOver escape gesture close the list.
Notes
- Uses
bg-controlfor the trigger,bg-surface-raisedfor the list andtext-linkfor the check mark; colors follow the system appearance. - On iOS the list renders inside a
FullWindowOverlayfromreact-native-screens, so it appears above native modals.