Prisma
Live Preview
Please enter a valid email address

Input Component Generation Skill

Skill này hướng dẫn bạn (AI Agent) tạo component Input / TextField — trường nhập liệu đơn dòng, nền tảng cho mọi form trong hệ thống.

1. Mục tiêu (Objective)

Tạo component Input hoàn chỉnh, hỗ trợ nhiều type (text, password, email, search, number), có validation states, và tích hợp 100% Design Tokens. Component phải tái sử dụng được, responsive, hỗ trợ Dark Mode tự động.

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

Khi nào dùng Input?

  • Nhập dữ liệu đơn dòng: tên, email, password, số điện thoại, tìm kiếm
  • Form fields: mọi trường input trong form đăng ký, đăng nhập, thanh toán

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

Tình huống Component đúng Lý do
Nhập text ngắn (1 dòng) Input Đơn dòng, submit khi Enter
Nhập text dài (nhiều dòng) Textarea Multi-line, auto-resize
Chọn từ danh sách có sẵn Select Predefined options, không tự nhập
Tìm kiếm với autocomplete Input (type=search) + Dropdown Input + suggestion list

Decision Tree cho AI

text
User cần nhập dữ liệu?
├─ Dữ liệu dạng text ngắn → Input
│   ├─ Có cần ẩn nội dung? → type="password"
│   ├─ Có cần validate email? → type="email"
│   ├─ Có cần tìm kiếm? → type="search" + icon
│   └─ Có cần chỉ số? → type="number"
├─ Dữ liệu dạng text dài → Textarea
└─ Chọn từ list → Select / Radio / Checkbox

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

3.1. Default / Outlined Input

  • Mục đích: Input chuẩn cho mọi form. Border rõ ràng giúp user nhận diện vùng nhập.
  • Đặc điểm: Nền trắng/trong suốt, border input-border, focus ring khi active.
  • Tương đồng: Outlined TextField (Material 3), Default TextField (iOS).

3.2. Filled Input

  • Mục đích: Dùng khi cần phân biệt Input với nền page (vd: search bar trên header tối).
  • Đặc điểm: Background base.muted, không có border rõ ràng ở trạng thái idle.
  • Tương đồng: Filled TextField (Material 3).

3.3. States

State Mô tả Token chính
Idle Trạng thái mặc định base.input, base.input-border
Focused User đang nhập base.primary (border), focus.ring-color
Error Validation fail base.destructive (border + message)
Success Validation pass base.success (border + icon)
Disabled Không tương tác opacity: base.disabled, cursor: not-allowed
Readonly Chỉ đọc, có thể copy Border nhạt, no focus ring

3.5. Slot Map (Figma ↔ Code)

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

Figma Slot data-slot CSS Class Required Accepts
Root input .input-group
Label input-label .label text
Field input-field .input text-input
Leading input-leading icon, prefix-text, currency-symbol
Trailing input-trailing icon, clear-button, suffix-text, eye-toggle
Helper input-helper helper-text, error-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ả
height.sm number {min-width.8} Small input height — compact forms, table inline edit
height.md number {min-width.10} Default input height — standard forms
height.lg number {min-width.12} Large input height — search bars, hero forms, mobile
padding-x number {spacing.3} Horizontal padding inside input fields
padding-y.sm number {spacing.1} Vertical padding for small inputs
padding-y.md number {spacing.2} Vertical padding for default inputs
padding-y.lg number {spacing.3} Vertical padding for large inputs
font-size.sm number {font.size.xs} Font size for small inputs
font-size.md number {font.size.sm} Font size for default inputs
font-size.lg number {font.size.base} Font size for large inputs
radius number {comp.radius} Corner radius — inherits from density tier comp.radius
border-width number {border-width.1} Default border width for input fields
border-width-focus number {border-width.2} Thicker border when input is focused
placeholder-color color {base.muted-foreground} Placeholder text color
color.background color {base.input} Default variant background
color.background-filled color {base.muted} Filled variant background
color.border-default color {base.input-border} Idle border
color.border-focus color {base.primary} Focus state border
color.border-error color {base.destructive} Error state border
color.border-success color {base.success} Success state border
color.text color {base.foreground} Input text color
color.label color {base.foreground} Label text color
color.helper color {base.muted-foreground} Helper text color
color.error-text color {base.destructive-muted-foreground} Error message color
color.focus-ring color {focus.ring-color} Focus ring color
color.disabled-bg color {base.muted} Disabled state background
color.disabled-fg color {base.muted-foreground} Disabled state foreground

| icon-size | number | {min-width.5} | Size of leading/trailing icons in input fields | | icon-gap | number | {spacing.2} | Gap between icon and input text | | transition | string | {duration.fast-2} {easing.standard} | Border/shadow transition on focus | | label.font-size | number | {font.size.sm} | Form label font size | | label.font-weight | number | {font.weight.medium} | Form label font weight | | label.margin-bottom | number | {spacing.1} | Gap between label and input field | | helper.font-size | number | {font.size.xs} | Helper/error text font size below input | | helper.margin-top | number | {spacing.1} | Gap between input field and helper text |

5. Props & API

typescript
interface InputProps {
  /** Visual variant */
  variant?: 'default' | 'filled';
  /** HTML input type */
  type?: 'text' | 'password' | 'email' | 'search' | 'number' | 'tel' | 'url';
  /** Size preset */
  size?: 'sm' | 'md' | 'lg';
  /** Label hiển thị phía trên */
  label?: string;
  /** Placeholder hint */
  placeholder?: string;
  /** Helper text bên dưới */
  helperText?: string;
  /** Error message — hiển thị thay helperText khi có lỗi */
  errorMessage?: string;
  /** Success state */
  isSuccess?: boolean;
  /** Disabled state */
  disabled?: boolean;
  /** Readonly state */
  readOnly?: boolean;
  /** Icon bên trái (search, user, mail...) */
  leftIcon?: ReactNode;
  /** Icon/action bên phải (clear, toggle password, copy...) */
  rightIcon?: ReactNode;
  /** Full width */
  fullWidth?: boolean;
}

6. Accessibility (a11y)

  • Label: Mỗi Input BẮT BUỘC phải có <label> liên kết qua htmlFor/id, hoặc aria-label nếu label ẩn.
  • Error: Khi error, thêm aria-invalid="true"aria-describedby trỏ đến error message.
  • Helper text: Liên kết qua aria-describedby.
  • Password toggle: Nút toggle show/hide password phải có aria-label="Show password" / "Hide password".
  • Search: Input type=search nên wrap trong <form role="search">.
  • Focus: :focus-visible outline rõ ràng, không dùng outline: none mà không thay thế.
  • Disabled: Thêm aria-disabled="true", không chỉ dùng attribute disabled.

7. Best Practices & Rules

  • Semantic HTML: Dùng <input> gốc, KHÔNG dùng <div contenteditable>.
  • Không Hardcode: Mọi màu sắc, spacing, radius, typography đều từ Token.
  • Icon: leftIcon/rightIcon CHỈ dùng Lucide icon từ assets/icons/ (vd: search, eye, mail, user). CẤM dùng text emoji. Xem icon.md mục 10.
  • Dark Mode: Tự động qua CSS Variables từ Mode tokens.
  • Responsive: Sử dụng fullWidth prop, padding từ Style Mode tokens.
  • Validation: Error state chỉ hiển thị sau khi user đã interact (onBlur), không hiển thị khi form mới load.
  • Password: Luôn có toggle show/hide, không tự động hiện password.

8. Domain-Specific Patterns

8.1. Password Toggle (show/hide)

Reference: MD3 Outlined TextField + trailing icon action, shadcn Input OTP toggle pattern

Aspect Spec
Trigger Trailing icon button (Lucide eye / eye-off)
Behavior Click toggles type between passwordtext
Icon tokens Color: --muted-foreground, hover: --foreground
Size --icon-md (20px), touch target: 44×44px (via padding)
a11y aria-label="Show password" / "Hide password", role="button"
Default type="password" (hidden) — user must opt-in to reveal
html
<!-- Password with toggle -->
<div class="input-group">
  <label for="pw">Mật khẩu</label>
  <div class="input-wrapper">
    <input id="pw" type="password" class="input" />
    <button type="button" class="input__trailing-action" aria-label="Show password">
      <svg class="icon icon--md"><!-- eye.svg --></svg>
    </button>
  </div>
</div>

8.2. Currency Input (VND formatting)

Reference: Fluent NumberField + suffix pattern, Ant Design InputNumber

Aspect Spec
Format Live formatting ###,### via Intl.NumberFormat('vi-VN')
Suffix Static text "VND" — --muted-foreground, caption text style
Input mode inputmode="numeric" (mobile numeric keypad)
Validation > 0, integers only, max 999,999,999
Behavior Format on input, store raw number, display formatted
Tokens All existing input tokens + suffix: --muted-foreground, --font-size-xs
html
<!-- Currency input -->
<div class="input-group">
  <label for="price">Giá bán</label>
  <div class="input-wrapper input-wrapper--suffix">
    <input id="price" type="text" inputmode="numeric" class="input" value="150,000" />
    <span class="input__suffix">VND</span>
  </div>
</div>
javascript
// Currency formatter utility
function formatCurrency(value) {
  const num = parseInt(value.replace(/\D/g, ''), 10);
  if (isNaN(num)) return '';
  return new Intl.NumberFormat('vi-VN').format(num);
}

8.3. Phone Auto-format (Vietnamese)

Reference: Apple HIG tel content type + OneUI interaction area

Aspect Spec
Pattern 0XXX XXX XXXX — auto-insert spaces at positions 4, 7
Validation 10 digits, starts with 0
Attributes type="tel", inputmode="numeric", autocomplete="tel"
Leading icon Lucide phone (optional)
Behavior Format on input, strip spaces for data
Tokens All existing input tokens
html
<!-- Phone input -->
<div class="input-group">
  <label for="phone">Số điện thoại</label>
  <div class="input-wrapper">
    <svg class="input__leading-icon icon icon--md"><!-- phone.svg --></svg>
    <input id="phone" type="tel" inputmode="numeric" autocomplete="tel"
           class="input" placeholder="0912 345 678" maxlength="12" />
  </div>
</div>
javascript
// Phone formatter utility
function formatPhone(value) {
  const digits = value.replace(/\D/g, '').slice(0, 10);
  if (digits.length <= 4) return digits;
  if (digits.length <= 7) return `${digits.slice(0, 4)} ${digits.slice(4)}`;
  return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`;
}

9. Example Usage

jsx
{/* Basic */}
<Input label="Email" type="email" placeholder="you@example.com" />

{/* Password with toggle */}
<Input label="Mật khẩu" type="password" showToggle />

{/* Currency */}
<Input label="Giá bán" format="currency" suffix="VND" inputMode="numeric" />

{/* Phone */}
<Input label="Số điện thoại" format="phone" type="tel" placeholder="0912 345 678" />

{/* Search variant */}
<Input variant="filled" type="search" placeholder="Tìm kiếm..." leftIcon={<SearchIcon />} size="lg" />

{/* With validation error */}
<Input label="Password" type="password" errorMessage="Mật khẩu phải có ít nhất 8 ký tự" />

{/* Disabled */}
<Input label="Username" value="ngocln" disabled />