Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

Input

Field văn bản một dòng trên nền nổi, bốn size, với icon trang trí hoặc văn bản cố định tùy chọn trước hoặc sau giá trị. Chữ là 16px trên màn hình cảm ứng để iOS Safari không phóng to khi focus.

  • npm: 1 trực tiếp, 2 tổng
  • registry: 2 trực tiếp, 2 tổng

Xem trước

Props

  • size
  • disabled
  • type
  • label
  • placeholder
  • defaultValue
  • icon
  • aria-invalid
import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-input" className="text-sm text-text-primary">Email</label>
  <Input id="preview-input" placeholder="you@example.com" />
</div>

Cách dùng

Khi nào dùng

  • Văn bản một dòng người dùng gõ: một tên, một địa chỉ email, một từ khóa tìm kiếm, một số.
  • Giá trị người dùng sao chép được nhưng không sửa được, chẳng hạn API key, với readOnly.
  • Field có icon cho biết nội dung nhập vào, chẳng hạn kính lúp cho tìm kiếm, với leadingIcon.

Khi nào không dùng

  • Văn bản nhiều dòng. Dùng Textarea.
  • Chọn một giá trị từ một danh sách đã biết. Dùng Select.
  • Một field disabled cho giá trị người dùng vẫn cần đọc hoặc sao chép. Dùng readOnly, để field vẫn focus được và giá trị sao chép được.

Biến thể

size

Chiều cao từ size-element-*: sm 28px, md 32px (mặc định), lg 36px, xl 44px khi dùng chuột. Trên cảm ứng, md tăng lên 36px, lg lên 44px và xl lên 48px; sm không tăng, nên đừng dùng sm trên màn hình ưu tiên cảm ứng.

  • sm

  • mdmặc định

  • lg

  • xl

import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-input-size-sm" className="text-sm text-text-primary">Email</label>
  <Input id="preview-input-size-sm" size="sm" placeholder="you@example.com" />
</div>
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-input-size-md" className="text-sm text-text-primary">Email</label>
  <Input id="preview-input-size-md" placeholder="you@example.com" />
</div>
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-input-size-lg" className="text-sm text-text-primary">Email</label>
  <Input id="preview-input-size-lg" size="lg" placeholder="you@example.com" />
</div>
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-input-size-xl" className="text-sm text-text-primary">Email</label>
  <Input id="preview-input-size-xl" size="xl" placeholder="you@example.com" />
</div>

Ví dụ

Mặc định

import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="email" className="text-sm text-text-primary">Email</label>
  <Input id="email" type="email" placeholder="you@example.com" />
</div>

Với một icon

Icon mang tính trang trí; label nhìn thấy đặt tên cho field.

import { IconSearch } from "@tabler/icons-react"
import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="search-files" className="text-sm text-text-primary">Tìm kiếm tệp</label>
  <Input id="search-files" type="search" placeholder="Tên hoặc phần mở rộng" leadingIcon={<IconSearch size={16} />} />
</div>

Với văn bản cố định

Văn bản cố định là một phần ý nghĩa của giá trị, nên input được mô tả bởi nó.

werewolf.dev/
kg
import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-md">
  <div className="flex w-full max-w-sm flex-col gap-xs">
    <label htmlFor="workspace-url" className="text-sm text-text-primary">Workspace URL</label>
    <Input id="workspace-url" leadingText="werewolf.dev/" defaultValue="acme" />
  </div>
  <div className="flex w-full max-w-sm flex-col gap-xs">
    <label htmlFor="weight" className="text-sm text-text-primary">Weight</label>
    <Input id="weight" type="number" inputMode="decimal" trailingText="kg" defaultValue="12" />
  </div>
</div>

Vô hiệu

import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="workspace" className="text-sm text-text-primary">Không gian làm việc</label>
  <Input id="workspace" disabled defaultValue="Personal" />
</div>

Chỉ đọc

import { IconLock } from "@tabler/icons-react"
import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="api-key" className="text-sm text-text-primary">Khóa API</label>
  <Input id="api-key" readOnly defaultValue="wolf_sk_4f9a..." trailingIcon={<IconLock size={16} />} />
</div>

Không hợp lệ

Nhập email như you@example.com.

import { Input } from "@/components/ui/input"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="signup-email" className="text-sm text-text-primary">Email</label>
  <Input id="signup-email" type="email" aria-invalid="true" aria-describedby="signup-email-error" defaultValue="not-an-email" />
  <p id="signup-email-error" className="text-sm text-text-danger">Nhập email như you@example.com.</p>
</div>

Nên và không nên

Nên

  • Luôn ghép input với một label nhìn thấy (htmlFor / id).
  • Khớp size của input với button bên cạnh: md trong form, sm chỉ trong UI đặc, ưu tiên chuột (bộ lọc table, toolbar), lg hoặc xl cho field chính duy nhất của một màn hình (search hero, đăng nhập trên mobile).
  • Không hợp lệ: aria-invalid="true", viền danger, cùng văn bản lỗi bằng color-text-danger liên kết với aria-describedby. Không bao giờ chỉ dùng màu.
  • Readonly: readOnly gốc, cùng kiểu với mặc định, vẫn focus được với vòng focus. Dùng cho giá trị người dùng sao chép được nhưng không sửa được (API key, URL được tạo).
  • Giữ viền danger khi đang focus; vòng focus được vẽ bên ngoài viền.
  • Dùng leadingIcon cho icon cho biết loại giá trị được nhập: kính lúp cho tìm kiếm, phong bì cho email.
  • Truyền icon kèm size, và size đó khớp size của input: 16 ở sm, md và lg, 20 ở xl.
  • Dùng leadingText hoặc trailingText cho phần cố định của giá trị mà người dùng không gõ: https://, đơn vị tiền, đơn vị đo. Giữ ngắn, một từ hoặc ký hiệu.

Không nên

  • Đừng dùng placeholder làm label hoặc cho thông tin bắt buộc; nó biến mất khi gõ.
  • Đừng dùng size sm trên màn hình ưu tiên cảm ứng. Trên cảm ứng md đã là 36px, lg 44px và xl 48px.
  • Đừng dùng disabled cho giá trị người dùng vẫn cần đọc hoặc sao chép; dùng readOnly.
  • Đừng thêm đổi viền khi hover. border-control đã đánh dấu field.
  • Đừng xuống dưới font-size-input hoặc tự đặt lại màu qua className.
  • Đừng để một icon tự mang ý nghĩa (dấu kiểm cho hợp lệ, cảnh báo cho không hợp lệ). Nói điều đó bằng văn bản liên kết với aria-describedby.
  • Đừng đặt button hoặc link vào leadingIcon hoặc trailingIcon. Icon mang tính trang trí và bị ẩn khỏi trình đọc màn hình.

Props

TênKiểuGiá trịMặc địnhMô tả
sizeenumsm | md | lg | xlmdChiều cao từ size-element-*: sm 28px, md 32px (mặc định), lg 36px, xl 44px khi dùng chuột. Trên cảm ứng, md tăng lên 36px, lg lên 44px và xl lên 48px; sm không tăng, nên đừng dùng sm trên màn hình ưu tiên cảm ứng.
disabledboolean-falseKhông tương tác. Kiểu disabled trung tính.
readOnlyboolean-falseGiá trị đọc, chọn và sao chép được nhưng không sửa được. Cùng kiểu với mặc định; giữ vòng focus.
typeenumtext | email | password | search | tel | url | numbertexttype của input gốc cho các field dạng văn bản một dòng. Chỉ trên web.
leadingIconnode--Icon Tabler trang trí trước văn bản, chẳng hạn kính lúp trong field tìm kiếm. Truyền size, và: 16 ở sm, md và lg, 20 ở xl.
trailingIconnode--Icon Tabler trang trí sau văn bản, chẳng hạn ổ khóa trên một giá trị readonly. Cùng quy tắc size với leadingIcon. Không dành cho button: button xóa hoặc hiện mật khẩu được lên kế hoạch thành prop riêng.
leadingTextnode--Văn bản cố định gắn vào đầu field, chẳng hạn https://, một tên miền hoặc đơn vị tiền. Vẽ thành một đoạn nền lõm, tách khỏi giá trị bằng một đường kẻ; input được mô tả bởi nó, nên trình đọc màn hình đọc nó cùng field.
trailingTextnode--Văn bản cố định gắn vào cuối field, chẳng hạn đơn vị (kg, %) hoặc tên miền (.com). Cùng kiểu và cách đọc với leadingText.

Slot

TênBắt buộcMô tả
valuekhôngGiá trị văn bản controlled hoặc uncontrolled.

Cấu tạo

PhầnMô tả
rootPhần tử input. Khi đứng một mình, đó là hộp field. Khi có icon, nó lấp đầy group và không vẽ hộp riêng.
placeholderVăn bản gợi ý hiện khi giá trị trống. Tô bằng text-secondary, nên đạt 4.5:1 trên nền nổi.
groupHộp field khi có icon hoặc văn bản cố định: một wrapper cùng nền, border, radius và chiều cao với input đứng một mình. Hiện trạng thái focus, kiểu disabled và viền danger của input thông qua :has(). className nằm ở đây.
groupIconIcon phía trước hoặc phía sau bên trong group. 16px ở sm, md và lg, 20px ở xl (DESIGN.md 9.1). Mang tính trang trí.
addonĐoạn leadingText hoặc trailingText: nền lõm, text-secondary, một đường border-border ở phía hướng về giá trị, sát hai mép của group.

Khả năng truy cập

Role: textbox

PhímHành động
TabChuyển focus tới input và hiện vòng focus.
Any printable keyChèn văn bản tại con nháy.
  • Ghép với một label nhìn thấy (htmlFor / id). Chỉ placeholder không phải là label.
  • Disabled dùng thuộc tính disabled gốc, nên input rời thứ tự tab.
  • Readonly dùng thuộc tính readOnly gốc: vẫn focus được và nằm trong thứ tự tab, giá trị chọn và sao chép được.
  • Không hợp lệ dùng aria-invalid="true" cùng văn bản lỗi nhìn thấy bằng color-text-danger liên kết với aria-describedby.
  • font-size-input là 14px với chuột và 16px trên cảm ứng để iOS Safari không phóng to trang khi focus.
  • leadingIcon và trailingIcon mang tính trang trí (aria-hidden). Label và văn bản mang ý nghĩa, nên trình đọc màn hình nghe cùng một field dù có hay không có chúng.
  • Một lần nhấn lên icon hoặc padding của group đưa focus vào input, giống nhấn lên chính field.

Cài đặt

npx werewolf-ui add input

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.