Skip to content

Tsai UI Blocks

Reusable section, empty-state, row, and list compositions backed by semantic tokens. The components accept widgets in their visual slots so application typography and business data remain outside the library.

Section Header example Empty State example List Item example List example

Section Header

TsaiSectionHeader(
  title: 'Transactions',
  trailingIcon: const TsaiIcon(LucideIcons.search),
  trailingIconSemanticLabel: 'Search transactions',
  onTrailingIconPressed: openSearch,
)

Empty State

TsaiEmptyState(
  icon: const TsaiIcon(LucideIcons.coffee, size: 28),
  title: 'No transactions yet',
  description: 'New transactions will show up here.',
  button: TsaiButton(
    label: 'Add money',
    size: TsaiButtonSize.medium,
    variant: TsaiButtonVariant.secondary,
    onPressed: addMoney,
  ),
)

List Item

content is required. icon, trailing, and the standard chevron are optional. The caller owns typography and layout inside content and trailing. Set active to display the selected background and provide onTap when the row is interactive.

TsaiListItem(
  icon: const TsaiIcon(LucideIcons.coffee, size: 20),
  content: const Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      TsaiTextBody(
        'Blue Bottle',
        size: TsaiBodySize.medium,
        weight: TsaiTextWeight.medium,
      ),
      TsaiTextCaption(
        'Coffee ยท Today',
        size: TsaiCaptionSize.medium,
        weight: TsaiTextWeight.regular,
      ),
    ],
  ),
  trailing: const TsaiTextMonoBody(
    '-\$4.50',
    size: TsaiBodySize.medium,
  ),
  showChevron: true,
  onTap: openTransaction,
)

List

TsaiList composes a TsaiSectionHeader, ordered TsaiListItem widgets, and an optional bottom button with the Penpot spacing contract.

TsaiList(
  title: 'Transactions',
  items: transactionItems,
  button: TsaiButton(
    label: 'Show all',
    size: TsaiButtonSize.medium,
    variant: TsaiButtonVariant.outline,
    isExpanded: true,
    onPressed: showAll,
  ),
)

List item icons are rendered through the public CircleIcon component, which keeps the Penpot 40-pixel circle and 20-pixel icon geometry consistent.