suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
GitHubGet started
DocsComponentsPatternsInteractionsChartsBlocks
Components82
  • 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
  • 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
  • Wheel Picker
  • Widget

suiss/ui · Component

Item

A flexible row that pairs media, a title, a description and actions, for lists and settings.

Item 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 { ShieldCheckIcon } from "@phosphor-icons/react"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

export default function ItemDemo() {
  return (
    <div className="w-96">
      <Item variant="outline">
        <ItemMedia variant="icon">
          <ShieldCheckIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Two-factor authentication</ItemTitle>
          <ItemDescription>
            Add an extra layer of security to your account.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            Enable
          </Button>
        </ItemActions>
      </Item>
    </div>
  )
}

Item is a row container. Inside it, ItemMedia holds an icon, image or avatar, ItemContent stacks an ItemTitle and ItemDescription, and ItemActions holds buttons or indicators at the end. ItemHeader and ItemFooter span the full width of the item, above and below the main row. The whole item can also render as a link or button.

Use the render prop to turn the whole item into a link or button.

Set asChild and pass a link or button as the child to turn the whole item into it.

Pass a render function that returns a link or button with the props it receives to turn the whole item into it.

ItemGroup stacks items as a list, and its inset variant groups them into a rounded list on bg-muted, like iOS Settings. Place an ItemSeparator between items to divide them. Use items to display content; for form controls with labels and validation, use Field.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/item

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 item

Manual

  1. Install the dependencies and add the cn helper:
$ pnpm add @base-ui/react class-variance-authority
  1. Copy the source into components/ui/item.tsx:
  1. Update the import paths to match your project.

Usage

import { ItemGroup, ItemSeparator, Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemHeader, ItemFooter } 
<div
















ItemGroup has the list role, so set role="listitem" on each Item inside it. Choose a variant for the item's border and background, and a size for its padding. Items inside a group with sm or xs items are spaced more tightly.

When ItemDescription is present, the media aligns to the top of the item instead of its center. The title is limited to one line and the description to two.

Examples

Variants

The default, outline and muted item variants.

Sizes

The default, sm and xs item sizes.

As link

An item rendered as a link, so the whole row is one focusable target.

With image

A list of people with image media, separators and an invite button on each row.

Inset

The same list in an inset item group, forming a rounded list with inset separators.

With avatar

An item with an Avatar as media and a Message button.

With header and footer

A plan summary with a price in the header and a renewal date and action in the footer.

API Reference

Anatomy17 parts
<Item>
<ItemMedia />
<ItemContent>
<ItemTitle />
<ItemDescription />
</ItemContent>
<ItemActions />
<ItemHeader>
01

ItemGroup

Stacks items as a list, or groups them into an inset list.

PropTypeDefaultDescription
variant"default" | "inset""default"inset groups the items into a rounded list on bg-muted, with square item corners and separators inset from the start.

Also accepts the props of React.ComponentProps<"div">.

02

ItemSeparator

A divider between items.

Also accepts the props of React.ComponentProps<"div">.

03

Item

The row container for media, content and actions.

PropTypeDefaultDescription
variant"default" | "outline" | "muted""default"default has no border or background, outline adds a border, and muted adds a subtle background.
size"default" | "sm" | "xs""default"Sets the padding and gap: default is the roomiest, then sm, then xs.

Also accepts the props of useRender.ComponentProps<"div">.

04

ItemMedia

Holds an icon, image or avatar at the start of the item.

PropTypeDefaultDescription
variant"default" | "icon" | "image""default"icon sizes an icon, image crops an image into a rounded square that scales with the item size, and default renders the media as is.

Also accepts the props of React.ComponentProps<"div">.

05

ItemContent

Stacks the title and description.

Also accepts the props of React.ComponentProps<"div">.

06

ItemTitle

The item's title, limited to one line.

Also accepts the props of React.ComponentProps<"div">.

07

ItemDescription

Supporting text, limited to two lines.

Also accepts the props of React.ComponentProps<"p">.

08

ItemActions

Holds buttons or indicators at the end of the item.

Also accepts the props of React.ComponentProps<"div">.

09

ItemHeader

A full-width row above the item's content.

Also accepts the props of React.ComponentProps<"div">.

10

ItemFooter

A full-width row below the item's content.

Also accepts the props of React.ComponentProps<"div">.

Accessibility

  • ItemGroup has the list role; give each Item in it the listitem role.
  • ItemSeparator has the none role, so it is ignored by assistive technology.
  • An item rendered as a link or button is a single focusable element and shows a focus ring.
  • Give icon-only action buttons an accessible name with aria-label.

Notes

  • Styling hooks: data-slot values item-group, item-separator, item, item-media, item-content, item-title, item-description, item-actions, item-header and item-footer.
  • Item sets data-variant and data-size; ItemGroup and ItemMedia set data-variant.
  • An item rendered as an anchor gets the accent background on hover.
  • Uses the border, muted, accent, muted-foreground and primary colors.

Item is a row that renders as a Pressable with the button role when it has onPress or onLongPress, and as a plain View with the listitem role otherwise. It has three variants (default, outline, muted) and three sizes (default, sm, xs).

ItemGroup stacks items as a list. Its inset variant joins them into one rounded card with square inner corners, which suits settings screens; add ItemSeparator between rows to draw an inset divider.

Installation

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

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

$ pnpm dlx @esuiss/ui@latest add item
$ pnpm dlx @esuiss/ui@latest add item --platform swift
NextKbdPreviousInput OTP

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • Variants
    • Sizes
    • As link
    • With image
    • Inset
    • With avatar
    • With header and footer
  4. 04API Reference
    • ItemGroup
    • ItemSeparator
    • Item
    • ItemMedia
    • ItemContent
    • ItemTitle
    • ItemDescription
    • ItemActions
    • ItemHeader
    • ItemFooter
  5. 05Accessibility
  6. 06Notes
  7. 07Installation
    • Manual
  8. 08Usage
  9. 09Examples
    • Inset group
  10. 10API Reference
    • ItemGroup
    • ItemSeparator
    • Item
    • ItemMedia
    • ItemContent
    • ItemTitle
    • ItemDescription
    • ItemActions
    • ItemHeader
    • ItemFooter
  11. 11Accessibility
  12. 12Notes
from
"@/components/ui/item"
className
=
"w-96"
>
<Item variant="outline">
<ItemMedia variant="icon">
<ShieldCheckIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Two-factor authentication</ItemTitle>
<ItemDescription>
Add an extra layer of security to your account.
</ItemDescription>
</ItemContent>
<ItemActions>
<Button variant="outline" size="sm">
Enable
</Button>
</ItemActions>
</Item>
</div>
<ItemTitle />
</ItemHeader>
<ItemFooter />
</Item>
<ItemGroup>
<ItemSeparator />
<Item>
<ItemMedia />
<ItemContent>
<ItemTitle />
<ItemDescription />
</ItemContent>
<ItemActions />
</Item>
</ItemGroup>
$ pnpm dlx @esuiss/ui@latest add item --platform kotlin

The CLI also adds what it builds on: Icon, Text.

Manual

  1. Install the dependencies:
npx expo install class-variance-authority cn
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/types.tsx, components/primitives/use-render.tsx.

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

uniwind/components/ui/item.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { Pressable, View, type ViewProps } from "react-native"
import type { RenderProp } from "@/components/primitives/types"
import { useRender } from "@/components/primitives/use-render"
import { IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"

type ItemSize = "default" | "sm" | "xs"

const ItemGroupContext = React.createContext<{ inset: boolean }>({
  inset: false,
})

const ItemContext = React.createContext<{ size: ItemSize }>({
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install class-variance-authority cn
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/types.tsx, components/primitives/use-render.tsx.

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

  1. Update the import paths to match your project.
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/types.tsx, components/primitives/use-render.tsx.

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

  1. Update the import paths to match your project.
  1. Target iOS 17 or later.

  2. Copy the theme files every view uses:

swift/Theme/Theme.swift
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/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/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 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.DpOffset
kotlin/theme/Tokens.kt
package 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 background: Color,
    val foreground: Color,
    val card: Color,
    val cardForeground: Color,
    val popover: Color,
    val popoverForeground: Color,
    val primary: Color,
    val primaryForeground: Color,
  1. Copy the source and every file it uses:
kotlin/components/Item.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
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.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
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/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/Header.kt
package com.esuiss.ui.components

import androidx.compose.animation.core.animate
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
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,
  1. Update the com.esuiss.ui package names to match your project.

Usage

import { ItemGroup, ItemSeparator, Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemHeader, ItemFooter } from "@/components/ui/item"
<View className="gap-4">
  <Item variant="outline">
    <ItemContent>
      <ItemTitle>Basic item</ItemTitle>
      <ItemDescription>
        A simple item with a title and a description.
      </ItemDescription>
    </ItemContent>
    <ItemActions>
      <Button variant="outline" size="sm">
        <Text>Action</Text>
      </Button>
    </ItemActions>
  </Item>
  <Item variant="muted" size="sm" onPress={() => undefined}>
    <ItemMedia variant="icon">
      <Icon as={ShieldCheckIcon} />
    </ItemMedia>
    <ItemContent>
      <ItemTitle>Your profile has been verified.</ItemTitle>
    </ItemContent>
    <ItemActions>
      <Icon as={CaretRightIcon} className="size-4 text-muted-foreground" />
    </ItemActions>
  </Item>
  <Item variant="outline" size="xs">
    <ItemHeader>
      <ItemTitle>Header</ItemTitle>
      <Badge>
        <Text>New</Text>
      </Badge>
    </ItemHeader>
    <ItemContent>
      <ItemDescription>
        Items can carry a header and a footer.
      </ItemDescription>
    </ItemContent>
    <ItemFooter>
      <Text className="text-sm text-muted-foreground">Footer</Text>
    </ItemFooter>
  </Item>
</View>

Compose a row from ItemMedia, ItemContent (holding ItemTitle and ItemDescription) and ItemActions. Use ItemHeader and ItemFooter for full-width rows above or below the main content.

ItemMedia with variant="icon" sizes icons to 16 points; variant="image" makes a square thumbnail that shrinks with the item size. ItemTitle truncates to one line and ItemDescription to two by default; override with numberOfLines. Text inside ItemActions picks up text-muted-foreground.

Pass render to render the row as another component while keeping the item styles.

Examples

Inset group

Pressable rows in an inset group, with colored icon tiles, separators and trailing chevrons.

iOS
Item (inset group) on iOS in light modeItem (inset group) on iOS in dark mode
import { BellIcon, CaretRightIcon, WifiHighIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Item, ItemActions, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemSeparator, ItemTitle } from "@/components/ui/item"
import { Text } from "@/components/ui/text"

export function ItemInsetGroupExample() {
  return (
    <ItemGroup variant="inset">
      <Item onPress={() => undefined}>
        <ItemMedia
          variant="image"
          className="bg-blue-500 items-center justify-center"
        >
          <Icon as={WifiHighIcon} className="size-5 text-primary-foreground" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Wi-Fi</ItemTitle>
        </ItemContent>
        <ItemActions>
          <Text>Home</Text>
          <Icon as={CaretRightIcon} className="size-4 text-muted-foreground" />
        </ItemActions>
      </Item>
      <ItemSeparator />
      <Item onPress={() => undefined}>
        <ItemMedia
          variant="image"
          className="bg-red-500 items-center justify-center"
        >
          <Icon as={BellIcon} className="size-5 text-primary-foreground" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Notifications</ItemTitle>
          <ItemDescription>Banners, sounds, badges</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Icon as={CaretRightIcon} className="size-4 text-muted-foreground" />
        </ItemActions>
      </Item>
    </ItemGroup>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct ItemInsetGroupExample: View {
  var body: some View {
    ItemGroup(.inset) {
      Item(action: {}) {
        ItemMedia(.image) {
          ItemTile(icon: Ph.wifiHigh.regular, color: EsuissColor.primary)
        }
        ItemContent {
          ItemTitle("Wi-Fi")
        }
        ItemActions {
          Text("Home")
          ItemChevron()
        }
      }
      ItemSeparator()
      Item(action: {}) {
        ItemMedia(.image) {
          ItemTile(icon: Ph.bell.regular, color: EsuissColor.destructive)
        }
        ItemContent {
          ItemTitle("Notifications")
          ItemDescription("Banners, sounds, badges")
        }
        ItemActions {
          ItemChevron()
        }
      }
    }
  }
}

struct ItemChevron: View {
  var body: some View {
    Ph.caretRight.regular
      .frame(width: 16, height: 16)
      .foregroundStyle(EsuissColor.mutedForeground)
  }
}

struct ItemTile: View {
  let icon: Image
  let color: Color

  var body: some View {
    ZStack {
      color
      icon
        .frame(width: 20, height: 20)
        .foregroundStyle(EsuissColor.primaryForeground)
    }
  }
}
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Bell
import com.adamglin.phosphoricons.regular.CaretRight
import com.adamglin.phosphoricons.regular.WifiHigh
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Item
import com.esuiss.ui.components.ItemActions
import com.esuiss.ui.components.ItemContent
import com.esuiss.ui.components.ItemDescription
import com.esuiss.ui.components.ItemGroup
import com.esuiss.ui.components.ItemGroupVariant
import com.esuiss.ui.components.ItemMedia
import com.esuiss.ui.components.ItemMediaVariant
import com.esuiss.ui.components.ItemSeparator
import com.esuiss.ui.components.ItemTitle
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Palette

@Composable
fun ItemCaret() {
    Icon(PhosphorIcons.Regular.CaretRight, contentDescription = null, size = 16.dp, tint = EsuissTheme.colors.mutedForeground)
}

@Composable
fun ItemSettingsMedia(icon: ImageVector, background: Color) {
    ItemMedia(variant = ItemMediaVariant.Image) {
        Box(Modifier.matchParentSize().background(background))
        Icon(icon, contentDescription = null, size = 20.dp, tint = EsuissTheme.colors.primaryForeground)
    }
}

@Composable
fun ItemInsetGroup() {
    val colors = EsuissTheme.colors
    ItemGroup(variant = ItemGroupVariant.Inset) {
        Item(onClick = {}) {
            ItemSettingsMedia(PhosphorIcons.Regular.WifiHigh, Palette.blue500)
            ItemContent {
                ItemTitle("Wi-Fi")
            }
            ItemActions {
                Text("Home")
                ItemCaret()
            }
        }
        ItemSeparator()
        Item(onClick = {}) {
            ItemSettingsMedia(PhosphorIcons.Regular.Bell, Palette.red500)
            ItemContent {
                ItemTitle("Notifications")
                ItemDescription("Banners, sounds, badges")
            }
            ItemActions { ItemCaret() }
        }
    }
}

API Reference

01

ItemGroup

List container for items.

PropTypeDefaultDescription
variant"default" | "inset""default"default stacks items with a gap; inset joins them into one rounded card.

Also accepts the props of ViewProps.

02

ItemSeparator

Divider between items, inset inside an inset group.

Also accepts the props of ViewProps.

03

Item

The row itself, pressable when given a press handler.

PropTypeDefaultDescription
variant"default" | "outline" | "muted""default"Visual style.
size"default" | "sm" | "xs""default"Padding and gap.
renderRenderProp-Renders the item as another element, such as a Pressable.

Also accepts the props of ItemProps.

04

ItemMedia

Leading icon, image or avatar.

PropTypeDefaultDescription
variant"default" | "icon" | "image""default"default, icon for 16-point icons, or image for a square thumbnail.

Also accepts the props of ViewProps.

05

ItemContent

Flexible column for the title and description.

Also accepts the props of ViewProps.

06

ItemTitle

Single-line title.

Also accepts the props of React.ComponentProps<typeof Text>.

07

ItemDescription

Secondary text, up to two lines.

Also accepts the props of React.ComponentProps<typeof Text>.

08

ItemActions

Trailing row for values, buttons or chevrons.

Also accepts the props of ViewProps.

09

ItemHeader

Full-width row above the content.

Also accepts the props of ViewProps.

10

ItemFooter

Full-width row below the content.

Also accepts the props of ViewProps.

Accessibility

  • ItemGroup has the list role.
  • A static Item has the listitem role; an item with onPress or onLongPress has the button role and fades to 80% opacity while pressed.
  • disabled dims the item and blocks presses.
  • ItemSeparator is hidden from VoiceOver and TalkBack.

Notes

  • Uses bg-muted for inset groups and the muted variant, border-border for outline, and opacity-80 for the pressed state.
  • Colors follow the system appearance.
iOS
Item on iOS in light modeItem on iOS in dark mode
import { CaretRightIcon, ShieldCheckIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemHeader, ItemMedia, ItemTitle } from "@/components/ui/item"
import { Text } from "@/components/ui/text"

export function ItemExample() {
  return (
    <View className="gap-4">
      <Item variant="outline">
        <ItemContent>
          <ItemTitle>Basic item</ItemTitle>
          <ItemDescription>
            A simple item with a title and a description.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            <Text>Action</Text>
          </Button>
        </ItemActions>
      </Item>
      <Item variant="muted" size="sm" onPress={() => undefined}>
        <ItemMedia variant="icon">
          <Icon as={ShieldCheckIcon} />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Your profile has been verified.</ItemTitle>
        </ItemContent>
        <ItemActions>
          <Icon as={CaretRightIcon} className="size-4 text-muted-foreground" />
        </ItemActions>
      </Item>
      <Item variant="outline" size="xs">
        <ItemHeader>
          <ItemTitle>Header</ItemTitle>
          <Badge>
            <Text>New</Text>
          </Badge>
        </ItemHeader>
        <ItemContent>
          <ItemDescription>
            Items can carry a header and a footer.
          </ItemDescription>
        </ItemContent>
        <ItemFooter>
          <Text className="text-sm text-muted-foreground">Footer</Text>
        </ItemFooter>
      </Item>
    </View>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct ItemExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 16) {
      Item(.outline) {
        ItemContent {
          ItemTitle("Basic item")
          ItemDescription("A simple item with a title and a description.")
        }
        ItemActions {
          Button("Action") {}
            .buttonStyle(.variant(.outline, size: .sm))
        }
      }
      Item(.muted, size: .sm, action: {}) {
        ItemMedia(.icon) {
          Ph.shieldCheck.regular
        }
        ItemContent {
          ItemTitle("Your profile has been verified.")
        }
        ItemActions {
          ItemChevron()
        }
      }
      Item(.outline, size: .xs) {
        ItemHeader {
          ItemTitle("Header")
          Badge("New")
        }
        ItemContent {
          ItemDescription("Items can carry a header and a footer.")
        }
        ItemFooter {
          Text("Footer")
            .textStyle(.sm)
            .foregroundStyle(EsuissColor.mutedForeground)
        }
      }
    }
  }
}

struct ItemChevron: View {
  var body: some View {
    Ph.caretRight.regular
      .frame(width: 16, height: 16)
      .foregroundStyle(EsuissColor.mutedForeground)
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.CaretRight
import com.adamglin.phosphoricons.regular.ShieldCheck
import com.esuiss.ui.components.Badge
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Item
import com.esuiss.ui.components.ItemActions
import com.esuiss.ui.components.ItemContent
import com.esuiss.ui.components.ItemDescription
import com.esuiss.ui.components.ItemFooter
import com.esuiss.ui.components.ItemHeader
import com.esuiss.ui.components.ItemMedia
import com.esuiss.ui.components.ItemMediaVariant
import com.esuiss.ui.components.ItemSize
import com.esuiss.ui.components.ItemTitle
import com.esuiss.ui.components.ItemVariant
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography

@Composable
fun ItemCaret() {
    Icon(PhosphorIcons.Regular.CaretRight, contentDescription = null, size = 16.dp, tint = EsuissTheme.colors.mutedForeground)
}

@Composable
fun ItemDefault() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Item(variant = ItemVariant.Outline, role = null) {
            ItemContent {
                ItemTitle("Basic item")
                ItemDescription("A simple item with a title and a description.")
            }
            ItemActions {
                Button("Action", onClick = {}, variant = ButtonVariant.Outline, size = ButtonSize.Sm)
            }
        }
        Item(variant = ItemVariant.Muted, size = ItemSize.Sm, onClick = {}) {
            ItemMedia(variant = ItemMediaVariant.Icon) {
                Icon(PhosphorIcons.Regular.ShieldCheck, contentDescription = null)
            }
            ItemContent {
                ItemTitle("Your profile has been verified.")
            }
            ItemActions { ItemCaret() }
        }
        Item(variant = ItemVariant.Outline, size = ItemSize.Xs, role = null) {
            ItemHeader {
                ItemTitle("Header")
                Badge("New")
            }
            ItemContent {
                ItemDescription("Items can carry a header and a footer.")
            }
            ItemFooter {
                Text("Footer", style = Typography.sm.copy(color = EsuissTheme.colors.mutedForeground))
            }
        }
    }
}
components/ui/item.tsx
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"
import type * as React from "react"
import { cn } from "@/lib/utils"

function ItemGroup({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<"div"> & { variant?: "default" | "inset" }) {
  return (
    <div
      role="list"
      data-slot="item-group"
      data-variant={variant}
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemTitle,
} from "@/components/ui/item"

export default function ItemVariants() {
  return (
    <div className="w-96">
      <div className="flex flex-col gap-4">
        {(["default", "outline", "muted"] as const).map((variant) => (
          <Item key={variant} variant={variant}>
            <ItemContent>
              <ItemTitle>
                {variant.charAt(0).toUpperCase() + variant.slice(1)} item
              </ItemTitle>
              <ItemDescription>
                A short description of this list entry.
              </ItemDescription>
            </ItemContent>
            <ItemActions>
              <Button variant="outline" size="sm">
                Open
              </Button>
            </ItemActions>
          </Item>
        ))}
      </div>
    </div>
  )
}
import { CaretRightIcon, CheckCircleIcon } from "@phosphor-icons/react"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

export default function ItemSizes() {
  return (
    <div className="w-96">
      <div className="flex flex-col gap-4">
        {(["default", "sm", "xs"] as const).map((size) => (
          <Item key={size} variant="outline" size={size}>
            <ItemMedia variant="icon">
              <CheckCircleIcon />
            </ItemMedia>
            <ItemContent>
              <ItemTitle>Your profile has been verified</ItemTitle>
            </ItemContent>
            <ItemActions>
              <CaretRightIcon className="size-4" />
            </ItemActions>
          </Item>
        ))}
      </div>
    </div>
  )
}
import { BellIcon, CaretRightIcon } from "@phosphor-icons/react"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

export default function ItemAsLink() {
  return (
    <div className="w-96">
      <Item variant="outline" render={<a href="#notifications" />}>
        <ItemMedia variant="icon">
          <BellIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Notification settings</ItemTitle>
          <ItemDescription>
            Choose what you want to be notified about.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <CaretRightIcon className="size-4" />
        </ItemActions>
      </Item>
    </div>
  )
}
import { PlusIcon } from "@phosphor-icons/react"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from "@/components/ui/item"

const people = [
  {
    name: "Olivia Martin",
    email: "[email protected]",
    avatar: "https://i.pravatar.cc/96?img=12",
  },
  {
    name: "Liam Chen",
    email: "[email protected]",
    avatar: "https://github.com/vercel.png",
  },
  {
    name: "Sofia Rossi",
    email: "[email protected]",
    avatar: "https://github.com/github.png",
  },
]

export default function ItemWithImage() {
  return (
    <div className="w-96">
      <ItemGroup>
        {people.map((person, index) => (
          <div key={person.email} className="contents">
            {index > 0 && <ItemSeparator />}
            <Item variant="default" size="sm" role="listitem">
              <ItemMedia variant="image">
                <img src={person.avatar} alt={person.name} />
              </ItemMedia>
              <ItemContent>
                <ItemTitle>{person.name}</ItemTitle>
                <ItemDescription>{person.email}</ItemDescription>
              </ItemContent>
              <ItemActions>
                <Button variant="ghost" size="icon-sm" aria-label="Invite">
                  <PlusIcon />
                </Button>
              </ItemActions>
            </Item>
          </div>
        ))}
      </ItemGroup>
    </div>
  )
}
import { PlusIcon } from "@phosphor-icons/react"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from "@/components/ui/item"

const people = [
  {
    name: "Olivia Martin",
    email: "[email protected]",
    avatar: "https://i.pravatar.cc/96?img=12",
  },
  {
    name: "Liam Chen",
    email: "[email protected]",
    avatar: "https://github.com/vercel.png",
  },
  {
    name: "Sofia Rossi",
    email: "[email protected]",
    avatar: "https://github.com/github.png",
  },
]

export default function ItemInset() {
  return (
    <div className="w-96">
      <ItemGroup variant="inset">
        {people.map((person, index) => (
          <div key={person.email} className="contents">
            {index > 0 && <ItemSeparator />}
            <Item variant="default" size="sm" role="listitem">
              <ItemMedia variant="image">
                <img src={person.avatar} alt={person.name} />
              </ItemMedia>
              <ItemContent>
                <ItemTitle>{person.name}</ItemTitle>
                <ItemDescription>{person.email}</ItemDescription>
              </ItemContent>
              <ItemActions>
                <Button variant="ghost" size="icon-sm" aria-label="Invite">
                  <PlusIcon />
                </Button>
              </ItemActions>
            </Item>
          </div>
        ))}
      </ItemGroup>
    </div>
  )
}
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

export default function ItemWithAvatar() {
  return (
    <div className="w-96">
      <Item variant="outline">
        <ItemMedia>
          <Avatar size="lg">
            <AvatarImage
              src="https://i.pravatar.cc/96?img=12"
              alt="Olivia Martin"
            />
            <AvatarFallback>OM</AvatarFallback>
          </Avatar>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Olivia Martin</ItemTitle>
          <ItemDescription>Last seen 5 minutes ago</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button size="sm">Message</Button>
        </ItemActions>
      </Item>
    </div>
  )
}
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemFooter,
  ItemHeader,
  ItemTitle,
} from "@/components/ui/item"

export default function ItemWithHeaderAndFooter() {
  return (
    <div className="w-96">
      <Item variant="outline">
        <ItemHeader>
          <ItemTitle>Pro plan</ItemTitle>
          <span className="text-sm font-medium">$24/mo</span>
        </ItemHeader>
        <ItemContent>
          <ItemDescription>
            Unlimited projects, priority support, and advanced analytics.
          </ItemDescription>
        </ItemContent>
        <ItemFooter>
          <span className="text-muted-foreground text-xs">Renews on May 1</span>
          <Button variant="outline" size="sm">
            Manage
          </Button>
        </ItemFooter>
      </Item>
    </div>
  )
}
nativewind/components/ui/item.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { Pressable, View, type ViewProps } from "react-native"
import type { RenderProp } from "@/components/primitives/types"
import { useRender } from "@/components/primitives/use-render"
import { IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"

type ItemSize = "default" | "sm" | "xs"

const ItemGroupContext = React.createContext<{ inset: boolean }>({
  inset: false,
})

const ItemContext = React.createContext<{ size: ItemSize }>({
stylesheet/components/ui/item.tsx
import * as React from "react"
import {
  Pressable,
  type PressableStateCallbackType,
  type StyleProp,
  StyleSheet,
  View,
  type ViewProps,
  type ViewStyle,
} from "react-native"
import type { RenderProp } from "@/components/primitives/types"
import { useRender } from "@/components/primitives/use-render"
import { IconSizeContext } from "@/components/ui/icon"
import { Text, TextStyleContext } from "@/components/ui/text"
import { fontSize, radius, useTheme } from "@/components/ui/theme"