Live Preview
General
Account
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.json→list· 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.mdfor 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>witharia-labeldescribing label-value pair - Action items:
<button>with descriptive label - Select items:
<input type="radio">in group, orrole="option"witharia-selected - Destructive items:
aria-labelnoting destructive nature - Focus:
:focus-visibleon 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 gets0 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;
}
}