Prisma
Live Preview

Radio Component Generation Skill

Skill này hướng dẫn bạn (AI Agent) tạo component Radio / Radio Group — điều khiển cho phép chọn DUY NHẤT một giá trị trong nhóm.

1. Mục tiêu (Objective)

Tạo component Radio Group hoàn chỉnh, đảm bảo mutual exclusivity (chỉ 1 option được chọn), hỗ trợ orientation, và tích hợp 100% Design Tokens.

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

Khi nào dùng Radio?

  • Chọn DUY NHẤT 1 từ nhóm 2–5 options
  • User cần thấy tất cả options cùng lúc để so sánh
  • Trong form (giá trị được submit cùng form)

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

Tình huống Component đúng Lý do
Chọn 1 từ 2–5 options, cần thấy hết Radio Group So sánh dễ
Chọn 1 từ > 5 options Select Tiết kiệm space
Chọn NHIỀU options Checkbox Group Multi-select
2 options dạng on/off Switch Toggle UX tốt hơn
2–3 options cùng hàng, compact Segmented Control / Tabs Inline switching

Decision Tree cho AI

text
User cần chọn 1 từ nhóm?
├─ 2–5 options, cần thấy hết → Radio Group
├─ > 5 options → Select (single)
├─ 2 options dạng toggle → Switch
├─ 2–3 options, inline switching → Segmented Control / Tabs
└─ Cần chọn nhiều → Checkbox Group

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

3.1. Radio States

State Visual Mô tả
Unselected Vòng tròn trống
Selected Vòng tròn + dot bên trong màu primary
Disabled ○ (mờ) Không tương tác
Error ○ (đỏ) Validation fail (chưa chọn option nào)

3.2. Radio Group Variants

  • Default: Vertical stack, radio + label.
  • Card Radio: Mỗi option là 1 card có border, selected state highlight — dùng khi option cần mô tả chi tiết (vd: chọn gói cước).

3.5. Slot Map (Figma ↔ Code)

📎 Source: slot-manifest.jsonradio · Layer: item

Figma Slot data-slot CSS Class Required Accepts
Root radio .radio
Indicator radio-indicator dot
Label radio-label text
Description radio-description text

4. Token Mapping

📦 Token values: Xem ATOMIC-MAPPING.md — single source of truth cho tất cả actual token values.

Token Type Value Mô tả
circle-size number {spacing.5} Radio circle dimensions (20px)
gap number {spacing.2} Gap giữa radio và label
group-gap number {spacing.3} Gap giữa radio items trong group
color.circle-border color {base.input-border} Unselected border
color.circle-border-selected color {base.primary} Selected border
color.inner-dot color {base.primary} Selected inner dot fill
color.circle-border-error color {base.destructive} Error border
color.label color {base.foreground} Label text color
color.description color {base.muted-foreground} Description text color
color.hover color {state-layer.hover} Hover state layer
color.card-border color {base.card-border} Card variant border
color.card-selected-bg color {base.primary-muted} Card variant selected bg
color.card-selected-border color {base.primary} Card variant selected border
color.focus-ring color {focus.ring-color} Focus ring — ref shared/focus
disabled-opacity number {opacity.50} Disabled opacity
transition string {duration.instant} {easing.standard} Selection transition
padding number {item.padding-default} Internal padding
radius number {comp.radius} Corner radius — inherits from density tier

5. Props & API

typescript
interface RadioGroupProps {
  /** Label của group */
  label?: string;
  /** Options */
  options: { value: string; label: string; description?: string; disabled?: boolean }[];
  /** Value đã chọn */
  value?: string;
  /** Orientation */
  orientation?: 'vertical' | 'horizontal';
  /** Visual variant */
  variant?: 'default' | 'card';
  /** Size */
  size?: 'sm' | 'md' | 'lg';
  /** Error message */
  errorMessage?: string;
  /** onChange callback */
  onChange?: (value: string) => void;
}

6. Accessibility (a11y)

  • Role: Dùng <input type="radio"> native với cùng name attribute.
  • Group: Wrap trong <fieldset> + <legend>, hoặc role="radiogroup" + aria-labelledby.
  • Keyboard: Arrow Up/Down hoặc Arrow Left/Right chuyển giữa options (roving tabindex), Tab ra ngoài group.
  • Required: aria-required="true" trên group nếu bắt buộc.
  • Error: aria-invalid="true" + aria-describedby cho error message.

7. Best Practices & Rules

  • Semantic HTML: Dùng native <input type="radio">, custom visual bằng CSS.
  • Mutual exclusivity: Chỉ cho phép 1 selection. Cùng name attribute.
  • Default selection: Thường nên có 1 option selected mặc định — tránh dùng Radio khi không cần mặc định.
  • Touch target: Vùng bấm ≥ 44×44px bao gồm label.
  • Không Hardcode: Mọi giá trị từ Token.

8. Example Usage

jsx
{/* Basic radio group */}
<RadioGroup
  label="Phương thức thanh toán"
  options={[
    { value: 'qr', label: 'QR Pay' },
    { value: 'card', label: 'Thẻ liên kết' },
    { value: 'bank', label: 'Chuyển khoản ngân hàng' },
  ]}
  value="qr"
/>

{/* Card variant */}
<RadioGroup
  variant="card"
  label="Chọn gói cước"
  options={[
    { value: 'basic', label: 'Basic', description: 'Miễn phí, giới hạn 5 giao dịch/ngày' },
    { value: 'pro', label: 'Pro', description: '99.000đ/tháng, không giới hạn' },
  ]}
/>