Prisma
Live Preview
General
Notifications
Dark Mode
Language
English
Privacy
Account
Profile
Payment
Sign Out
Your notification and language preferences are synced across all devices.

--- description: Hướng dẫn Agent tự động tạo UI Component List Group — nhóm list items trong shared card container, kiểu iOS Settings / grouped table. Tích hợp trends iOS 26 Liquid Glass, Material Design 3 Expressive, Samsung OneUI 7.

List Group Component Generation Skill

Skill này hướng dẫn bạn (AI Agent) tạo component List Group — nhóm nhiều list items liên quan trong 1 shared card container, với dividers giữa items (kiểu iOS Settings, Material grouped list).

1. Mục tiêu (Objective)

Tạo grouped list component hỗ trợ inset-grouped layout (iOS), container-based layout (MD3 Expressive), multiple item types (toggle, link, info, action), và tương thích iOS 26 Liquid Glass + Material Design 3 Expressive containers + Samsung OneUI 7 grouped settings.

2. AI Context & Intent (Ngữ cảnh cho AI)

Khi nào dùng List Group?

  • Settings / Preferences: nhóm toggles, links, info theo category
  • Detail info: boarding pass data, profile info, order summary
  • Related actions: nhóm các hành động cùng context
  • Form sections: nhóm form fields theo nhóm logic

Phân biệt với component khác

Tình huống Component đúng Lý do
Nhóm items trong shared card, dividers List Group Structured, grouped
Free-form card content Card No item-level structure
Data table (columns, sortable) Table Columns, headers
Simple vertical list no container Unstyled list No card wrapper
Horizontal scrollable items Scroll container Horizontal, not grouped
Accordion with expand/collapse Accordion Collapsible sections

Decision Tree cho AI

text
Cần hiển thị list of items?
├─ Trong shared card, dividers giữa items → List Group
│   ├─ Settings-style (toggles, chevrons) → List Group (interactive)
│   ├─ Info-display (label: value pairs) → List Group (info)
│   ├─ Mixed (some tap, some toggle) → List Group (mixed)
│   └─ Glass surface → List Group variant="glass"
├─ Data with columns → Table
├─ Expandable sections → Accordion
├─ Individual cards → Card in grid/stack
└─ No visual container → Plain <ul>

3. Platform Trend Research (2025-2026)

3.1. iOS 26 — Grouped List (InsetGroupedListStyle)

Feature Spec
Style InsetGroupedListStyle — inset from edges, rounded corners
Material Liquid Glass — frosted, translucent group container
Container Card-like, rounded corners, inset from screen edges
Radius 10pt per group (iOS 15+), larger in iOS 26 (~16pt)
Dividers 1px separator, left-inset (aligned after leading icon)
Header Section header above group (uppercase, muted, 12px)
Footer Section footer below group (muted, helper text)
Row height 44pt standard, taller with subtitle
Content Leading icon + Title + Subtitle + Trailing (chevron/toggle/value)
Swipe actions Leading/trailing swipe for delete, archive, etc.
Selection Checkmark on trailing edge
Glass treatment Container uses glass material, items inherit
Disclosure Chevron for drill-down links

3.2. Material Design 3 Expressive — Lists

Feature Spec
Container Heavy use of containers in M3 Expressive
Item height 56dp (1-line), 72dp (2-line), 88dp (3-line)
Leading Avatar, icon, checkbox, radio, image, monogram
Trailing Text, icon, checkbox, switch, icon button
Dividers Full-bleed or inset (after leading element)
Selection Checkbox (multi), Radio (single), highlight bg
Actionability Entire row tappable, or specific trailing action
Overline Small caps text above title for category
Supporting text 1-2 lines below title
Button groups New component for related action buttons
Adaptability Layout adjusts margins/density per window size

3.3. Samsung OneUI 7 — Grouped Settings

Feature Spec
Container Rounded card, white/dark background
Radius 16-20dp per group
Dividers Thin, left-inset
Row height 52dp standard, 68dp with subtitle
Header Bold section title, above group, left-aligned
Toggle Rounded switch on trailing
Chevron Right arrow > for drill-down
Color accents Toggle uses accent color, not system blue
Summary text Below title, smaller/muted (e.g., current value)
Glass preview (8.5) Frosted glass groups, transparency
Minimalism Clean, no excessive decoration

3.4. Cross-platform Convergence

Feature iOS 26 MD3 Exp OneUI 7
Container radius 16pt 12-16dp 16-20dp
Divider inset After icon Full or after icon After icon
Section header Uppercase, muted Normal case, medium Bold, normal case
Trailing chevron ✅ (disclosure) ✅ (icon) ✅ (arrow)
Glass container ✅ (8.5 preview)
Swipe actions ✅ native ❌ standard ✅ limited
Item height 44pt 56dp 52dp

4. Ngữ nghĩa & Phân loại (Semantics)

4.1. Visual Variants

Variant Mô tả Use case Platform feel
default Solid card background, rounded Standard settings Cross-platform
inset Card inset from screen edges iOS Settings style iOS
glass Liquid glass container Premium/luxury apps iOS 26
plain No card wrapper, just dividers Flat lists Minimal

4.2. Sub-components / Slots

Part Mô tả Tokens chính
Group Container Card wrapper, rounded corners --card, --section-radius-default
Group Header Section title above group caption or overline text style, --muted-foreground
Group Footer Helper text below group caption text style, --muted-foreground
List Item Individual row No individual radius/shadow
Item Leading Icon / Avatar / Checkbox — use ds-leadicon for standardized icon containers. See §10 Style Decision Matrix for mode/shape/color/size guidance. --icon-md, --primary / --muted-foreground
Item Content Title + Optional subtitle body-default / body-small
Item Trailing Chevron / Switch / Value / Badge --muted-foreground (chevron), component tokens
Divider Separator between items --border, 1px, inset from leading

4.3. Item Types

Type Leading Content Trailing Behavior
link Icon (opt) Title + Subtitle Chevron Tap → navigate
toggle Icon (opt) Title + Subtitle Switch Toggle on/off
info Icon (opt) Label Value text Display only
action Icon (opt) Title Tap → action
select Icon (opt) Title Checkmark ✓ Tap → select
destructive Icon (opt) Title (red) Tap → confirm
stepper Image/Icon (opt) Title + Subtitle (price) [-] count [+] Increment/decrement qty

4.4. Interactive States

State Visual Token
Idle Default bg from card --card bg
Hover (item) Subtle background --state-layer-hover
Pressed Darker background --state-layer-focused
Focus (keyboard) Focus ring on item --primary outline
Disabled Reduced opacity opacity: var(--disabled)
Selected Checkmark + subtle bg --primary-muted bg

4.5. Divider Rules

Scenario Divider style
Between items 1px solid var(--border), inset from leading edge
Before first item None
After last item None
Between groups Spacing gap, no line
Full-bleed option Divider extends full width
Inset option Starts after leading icon width + padding

4.6. Slot Map (Figma ↔ Code)

📎 Source: slot-manifest.jsonlist · Layer: card

Figma Slot data-slot CSS Class Required Accepts
Root list .card-listgroup
Header list-header .card-listgroup__header section-title (overline)
Item list-item .card-listgroup__item ✅ (n×) list-item
Leading list-leading .card-listgroup__icon icon, avatar, checkbox, radio, thumbnail
Content list-content .card-listgroup__content text
Title list-title .card-listgroup__title text
Subtitle list-subtitle .card-listgroup__subtitle text
Trailing list-trailing .card-listgroup__trailing chevron, switch, value-text, icon-button, badge
Footer list-footer .card-listgroup__footer helper-text (caption)
Separator list-separator .card-listgroup__item + .card-listgroup__item full-width, inset
html
<!-- HTML skeleton with data-slot -->
<section class="card-listgroup" data-slot="list">
  <div class="card-listgroup__header" data-slot="list-header">SECTION</div>
  <button class="card-listgroup__item" data-slot="list-item">
    <span class="card-listgroup__icon" data-slot="list-leading">📱</span>
    <span class="card-listgroup__content" data-slot="list-content">
      <span class="card-listgroup__title" data-slot="list-title">Title</span>
      <span class="card-listgroup__subtitle" data-slot="list-subtitle">Subtitle</span>
    </span>
    <span class="card-listgroup__trailing" data-slot="list-trailing">›</span>
  </button>
</section>

5. Token Mapping

📦 Atomic Mapping: See ATOMIC-MAPPING.md for complete token spec.

📦 Atomic Mapping: UI Layer: Card, Density Tier: section (container) + comp (rows)

Property Token Ghi chú
Group bg --card Container background
Group fg --card-foreground
Group border --border Optional outer border
Group radius --section-radius-default 12px default
Group padding 0 Items handle own padding
Item padding var(--comp-padding-default) var(--section-padding-default) 12px vertical, 24px horizontal
Item min-height 44-56px --spacing-11 to --spacing-14
Title font body-default text style 16px, normal
Title font weight --font-weight-normal
Subtitle font body-small + --muted-foreground 14px, muted
Value font body-default + --muted-foreground Trailing text
Header font overline text style 12px, semibold, wide tracking
Header fg --muted-foreground
Header padding var(--comp-padding-default) var(--section-padding-default)
Footer font caption text style 12px, muted
Divider color --border
Divider thickness 1px
Divider inset-start var(--spacing-14) After icon (56px from left)
Leading icon size --icon-md (20px)
Leading icon color --primary or --muted-foreground
Leading icon bg --primary-muted (if in circle) Optional icon circle
Leading icon radius --comp-radius If square-icon style
Chevron icon --muted-foreground, 16px Trailing disclosure
Trailing value fg --muted-foreground
Hover bg --state-layer-hover
Selected bg --primary-muted
Destructive fg --destructive Red text for destructive items
Transition --motion-hover Hover/press feedback
Shadow (default) --shadow-xs or none Less prominent than regular card
Elevation level-0 to level-1 Card layer

Glass variant tokens

Property Value Note
Glass bg var(--card) Uses existing translucent card token
Backdrop blur(30px) saturate(160%) Decorative OK
Glass border 1px solid var(--border) Token-based
Item divider on glass var(--border) with lower opacity Subtle

6. Props & API

typescript
interface ListGroupProps {
  /** Visual variant */
  variant?: 'default' | 'inset' | 'glass' | 'plain';
  /** Section header text */
  header?: string;
  /** Section footer/helper text */
  footer?: string;
  /** List items */
  children: ReactNode;
}

interface ListItemProps {
  /** Item type determines trailing element */
  type?: 'link' | 'toggle' | 'info' | 'action' | 'select' | 'destructive' | 'stepper';
  /** Leading icon */
  icon?: ReactNode;
  /** Leading icon background color */
  iconBg?: string;
  /** Title text */
  title: string;
  /** Secondary text below title */
  subtitle?: string;
  /** Trailing value text (for info type) */
  value?: string;
  /** Toggle state (for toggle type) */
  checked?: boolean;
  /** Selected state (for select type) */
  selected?: boolean;
  /** Quantity (for stepper type) */
  quantity?: number;
  /** Min quantity (for stepper type, default: 1) */
  minQuantity?: number;
  /** Max quantity (for stepper type, default: undefined = no limit) */
  maxQuantity?: number;
  /** Quantity change handler (for stepper type) */
  onQuantityChange?: (quantity: number) => void;
  /** Click/tap handler */
  onClick?: () => void;
  /** Toggle change handler */
  onToggle?: (checked: boolean) => void;
  /** Navigation href (for link type) */
  href?: string;
  /** Disabled state */
  disabled?: boolean;
  /** Divider style */
  divider?: 'inset' | 'full' | 'none';
}

7. Accessibility (a11y)

  • Semantic HTML: <section> wrapping <ul role="list"> + <li> for items
  • Header: <h2> or <h3> for section header (proper hierarchy)
  • Link items: <a> tag with descriptive text
  • Toggle items: <label> wrapping title + <input type="checkbox" role="switch">
  • Info items: <div> with aria-label describing label-value pair
  • Action items: <button> with descriptive label
  • Select items: <input type="radio"> in group, or role="option" with aria-selected
  • Destructive items: aria-label noting destructive nature
  • Focus: :focus-visible on each interactive item, skip non-interactive (info)
  • Keyboard: Arrow keys to navigate between items, Enter/Space to activate
  • Touch target: Min 44px height per item (WCAG 2.2)
  • Screen reader: Announce group header, item count, item type
  • Reduced motion: Switch toggle animation respects prefers-reduced-motion

8. Best Practices & Rules

  • Không Hardcode: All colors, spacing, radius from tokens
  • No individual card radius: Items inside group have NO border-radius — only container has radius
  • First/last item radius: First item gets border-radius: var(--section-radius-default) var(--section-radius-default) 0 0, last gets 0 0 radius radius
  • Divider inset: Dividers start AFTER leading icon area (not from edge)
  • No divider edges: No divider before first item or after last item
  • Dark Mode: Automatic via Mode tokens
  • Max items per group: 7-10 recommended. More → split into sub-groups with headers
  • Consistent types: Don't mix too many item types in one group. Keep 1-2 types per group.
  • Group spacing: Between groups, use var(--block-stack-gap-default) gap
  • Glass variant: Only with liquid/glass styles
  • Header style: iOS = uppercase, muted. Material = normal case, medium weight. OneUI = bold, normal case
  • Responsive: On tablet/desktop, inset variant adds more inline margin

9. Example Usage

jsx
{/* Settings-style group */}
<ListGroup header="GENERAL" footer="Your notification preferences affect all devices.">
  <ListItem type="toggle" icon={<BellIcon />} title="Notifications" checked onToggle={...} />
  <ListItem type="toggle" icon={<MoonIcon />} title="Dark Mode" checked={false} onToggle={...} />
  <ListItem type="link" icon={<GlobeIcon />} title="Language" value="English" href="/settings/lang" />
  <ListItem type="link" icon={<LockIcon />} title="Privacy" href="/settings/privacy" />
</ListGroup>

{/* Info display (boarding pass style) */}
<ListGroup variant="inset">
  <ListItem type="info" title="Passenger" value="Jimmy Anton" />
  <ListItem type="info" title="Date" value="Dec 12, 2025" />
  <ListItem type="info" title="Class" value="Economic" />
  <ListItem type="info" title="Seat" value="30 A" />
  <ListItem type="info" title="Gate" value="18" />
</ListGroup>

{/* Glass group */}
<ListGroup variant="glass" header="Account">
  <ListItem type="link" icon={<UserIcon />} iconBg="primary" title="Profile" href="/profile" />
  <ListItem type="link" icon={<CreditCardIcon />} iconBg="success" title="Payment" href="/payment" />
  <ListItem type="destructive" icon={<LogOutIcon />} title="Sign Out" onClick={signOut} />
</ListGroup>

{/* Mixed actions */}
<ListGroup>
  <ListItem type="action" icon={<ShareIcon />} title="Share" onClick={share} />
  <ListItem type="action" icon={<CopyIcon />} title="Copy Link" onClick={copyLink} />
  <ListItem type="destructive" icon={<TrashIcon />} title="Delete" onClick={confirmDelete} />
</ListGroup>

{/* Cart / Order items with stepper (xPOS pattern) */}
<ListGroup header="GIỎ HÀNG">
  <ListItem
    type="stepper"
    icon={<img src="/product-1.jpg" alt="Cà phê sữa" />}
    title="Cà phê sữa đá"
    subtitle="29,000 VND"
    quantity={2}
    minQuantity={1}
    onQuantityChange={(qty) => updateCart('product-1', qty)}
  />
  <ListItem
    type="stepper"
    title="Bánh mì thịt"
    subtitle="35,000 VND"
    quantity={1}
    minQuantity={1}
    onQuantityChange={(qty) => updateCart('product-2', qty)}
  />
</ListGroup>

10. CSS Skeleton

css
/* ─── Group Container — Card layer ─── */
.card-listgroup {
  background: var(--card);
  border-radius: var(--section-radius-default);
  border: 1px solid var(--border);
  overflow: hidden;
  color: var(--card-foreground);
}

/* Inset variant */
.card-listgroup--inset {
  margin: 0 var(--spacing-4);
}

/* Glass variant */
.card-listgroup--glass {
  background: var(--card);
  backdrop-filter: blur(30px) saturate(160%);
  -webkit-backdrop-filter: blur(30px) saturate(160%);
  border: 1px solid var(--border);
}

/* Plain variant — no container */
.card-listgroup--plain {
  background: transparent;
  border: none;
  border-radius: 0;
}

/* ─── Section Header ─── */
.card-listgroup__header {
  padding: var(--spacing-3) var(--spacing-5) var(--spacing-1-5);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--font-tracking-widest);
  text-transform: uppercase;
  color: var(--muted-foreground);
}

/* ─── Section Footer ─── */
.card-listgroup__footer {
  padding: var(--spacing-1-5) var(--spacing-5) var(--spacing-3);
  font-size: var(--font-size-xs);
  color: var(--muted-foreground);
  line-height: var(--font-leading-ratio-normal);
}

/* ─── List Item ─── */
.card-listgroup__item {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  min-height: var(--spacing-11); /* 44px touch target */
  padding: var(--spacing-3) var(--spacing-5);
  background: transparent;
  border: none;
  width: 100%;
  text-align: left;
  font-family: inherit;
  font-size: var(--font-size-base);
  color: var(--card-foreground);
  cursor: pointer;
  transition: background var(--duration-fast-2) var(--easing-standard);
  position: relative;
  text-decoration: none;
}

.card-listgroup__item:hover {
  background: var(--state-layer-hover);
}

.card-listgroup__item:active {
  background: var(--state-layer-focused);
}

/* Info items — not interactive */
.card-listgroup__item--info {
  cursor: default;
}
.card-listgroup__item--info:hover {
  background: transparent;
}

/* Destructive */
.card-listgroup__item--destructive {
  color: var(--destructive);
}

/* Disabled */
.card-listgroup__item--disabled {
  opacity: var(--disabled);
  pointer-events: none;
}

/* ─── Item Leading Icon ─── */
.card-listgroup__icon {
  width: var(--spacing-7); /* 28px */
  height: var(--spacing-7);
  border-radius: var(--comp-radius); /* density-scaled — iOS style */
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--primary-muted);
  color: var(--primary);
}

.card-listgroup__icon svg {
  width: var(--icon-sm); /* 16px */
  height: var(--icon-sm);
}

.card-listgroup__icon--success { background: var(--success-muted); color: var(--success); }
.card-listgroup__icon--warning { background: var(--warning-muted); color: var(--warning); }
.card-listgroup__icon--destructive { background: var(--destructive-muted); color: var(--destructive); }
.card-listgroup__icon--info { background: var(--info-muted); color: var(--info); }

/* No icon bg variant (Android/OneUI style) */
.card-listgroup__icon--plain {
  background: transparent;
  color: var(--muted-foreground);
}
.card-listgroup__icon--plain svg {
  width: var(--icon-lg); /* 24px */
  height: var(--icon-lg);
}

/* ─── Item Content ─── */
.card-listgroup__content {
  flex: 1;
  min-width: 0;
}

.card-listgroup__title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-normal);
  color: inherit;
}

.card-listgroup__subtitle {
  font-size: var(--font-size-sm);
  color: var(--muted-foreground);
  margin-top: var(--spacing-0-5);
}

/* ─── Item Trailing ─── */
.card-listgroup__trailing {
  display: flex;
  align-items: center;
  gap: var(--spacing-1-5);
  flex-shrink: 0;
}

.card-listgroup__value {
  font-size: var(--font-size-base);
  color: var(--muted-foreground);
}

.card-listgroup__chevron {
  width: var(--icon-sm);
  height: var(--icon-sm);
  color: var(--muted-foreground);
  opacity: calc(var(--opacity-50, 50) / 100);
}

/* ─── Divider ─── */
.card-listgroup__item + .card-listgroup__item {
  border-top: 1px solid var(--border);
}

/* Inset divider — after leading icon area */
.card-listgroup--inset-dividers .card-listgroup__item + .card-listgroup__item {
  border-top: none;
}
.card-listgroup--inset-dividers .card-listgroup__item + .card-listgroup__item::before {
  content: '';
  position: absolute;
  top: 0;
  left: var(--spacing-14); /* 56px — after icon */
  right: 0;
  height: 1px;
  background: var(--border);
}

/* ─── Focus ─── */
.card-listgroup__item:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: -2px;
  border-radius: var(--comp-radius);
}

/* ─── Selected (checkmark) ─── */
.card-listgroup__item--selected {
  background: var(--primary-muted);
}

/* ─── Stepper (Quantity Control) ─── */
.card-listgroup__stepper {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}

.card-listgroup__stepper-btn {
  width: var(--spacing-8); /* 32px */
  height: var(--spacing-8);
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--foreground);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background var(--duration-fast-2) var(--easing-standard);
  font-size: var(--font-size-base);
  font-family: inherit;
  padding: 0;
}

.card-listgroup__stepper-btn:hover {
  background: var(--state-layer-hover);
}

.card-listgroup__stepper-btn:disabled {
  opacity: var(--disabled);
  pointer-events: none;
}

.card-listgroup__stepper-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.card-listgroup__stepper-count {
  min-width: var(--spacing-8);
  text-align: center;
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--foreground);
}

/* Stepper item leading image */
.card-listgroup__item--stepper .card-listgroup__icon {
  width: var(--spacing-10); /* 40px */
  height: var(--spacing-10);
  border-radius: var(--comp-radius);
  overflow: hidden;
  background: var(--muted);
}

.card-listgroup__item--stepper .card-listgroup__icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .card-listgroup__item,
  .card-listgroup__stepper-btn {
    transition: none;
  }
}