輸入元件
表單欄位
表單欄位容器 React 元件:標籤、說明、錯誤訊息與前後綴整合,錯誤以高度展開收合不跳版,前後綴與輸入框共用聚焦外框,可用 render prop 包任何控制項。
把一個表單欄位該有的東西收進同一個容器:標籤、標籤下的說明、欄位下的提示、錯誤訊息、必填星號、前後綴與字數。錯誤出現時以高度從 0 展開、消失時反向收合,下方欄位會平順地讓位而不是突然跳動;錯誤文字改變時只交叉淡接,不重做高度動畫。前綴、輸入框、後綴包在同一個外框裡,聚焦時一起亮起 ring,並有一條 2px 底線從中心向兩側展開。不給 children 時渲染內建 input(Enter 有 IME 組字保護、字數以字元計算);給 render prop 就能包你自己的 select、textarea 或任何控制項,id、aria-describedby、aria-invalid 都會自動接好。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add form-fieldPlayground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
180
編輯中的呼叫預設屬性
<FormField/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
安裝
npx webberui@latest add https://webberui.com/r/form-field.json或在 components.json 設定 registries 後,改用 @webberui/form-field 安裝。
使用
import { Mail } from "lucide-react";
import { FormField } from "@/components/ui/form-field";
// 內建 input
<FormField
label="電子郵件"
required
prefix={<Mail aria-hidden />}
hint="我們不會公開你的電子郵件"
error={emailError}
inputProps={{
type: "email",
value: email,
onChange: (value) => setEmail(value),
onEnter: () => submit(),
}}
/>
// render prop:包你自己的控制項
<FormField label="所在縣市" required error={cityError}>
{(field) => (
<select {...field} value={city} onChange={(e) => setCity(e.target.value)}>
<option value="">請選擇</option>
<option value="臺北市">臺北市</option>
</select>
)}
</FormField>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
label | ReactNode | — | 欄位標籤 |
description | ReactNode | — | 標籤下方的說明文字 |
hint | ReactNode | — | 欄位下方的灰色提示;error 出現時被取代 |
error | ReactNode | — | 錯誤訊息;有值即進入錯誤狀態 |
required | boolean | false | 標籤後加紅色「*」,並對內建 input 加 required 與 aria-required |
orientation | "vertical" | "horizontal" | "vertical" | 排列方向;horizontal 時標籤欄固定寬並靠右,手機寬度自動回落為 vertical |
prefix | ReactNode | — | 輸入框前綴:文字(灰底分段)、icon 或按鈕(透明) |
suffix | ReactNode | — | 輸入框後綴:文字(灰底分段)、icon 或按鈕(透明) |
maxLength | number | — | 字數上限(以 Array.from 計算字元,中文與 emoji 各算一字);僅內建 input 生效 |
showCount | boolean | false | 在右下角顯示字數「12 / 50」;僅內建 input 生效 |
duration | number | 180 | 錯誤訊息展開收合的動畫時間(毫秒) |
disabled | boolean | false | 停用欄位 |
inputProps | FormFieldInputProps | — | 內建 input 的屬性 |
children | (field: FormFieldControlProps) => ReactNode | — | render prop;不給時渲染內建 input |
className | string | — | 透傳到最外層容器 |
FormFieldInputProps
其餘原生 <input> 屬性(type、placeholder、name、onBlur、autoComplete 等)都會透傳。
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
value | string | — | 受控輸入值;未提供時由元件內部管理(非受控) |
defaultValue | string | "" | 非受控模式的初始值 |
onChange | (value: string, event) => void | — | 輸入值變動時回呼(已依 maxLength 以字元數截斷) |
onEnter | (value: string, event) => void | — | 按下 Enter 時回呼;IME 組字中按 Enter 不會觸發 |
FormFieldControlProps
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | — | 由 useId 產生,標籤以 htmlFor 指向它 |
aria-describedby | string | undefined | — | 串起說明、提示/錯誤、字數的 id |
aria-invalid | boolean | — | 是否處於錯誤狀態 |
aria-required | true | undefined | — | required 時為 true |
disabled | boolean | undefined | — | 是否停用 |
className | string | — | 去除邊框與背景的控制項樣式,讓它與前後綴共用外框 |
細節
- 不跳版的錯誤動畫:錯誤區以
height: 0 → auto加淡入展開,時間由duration控制(預設 180ms、easeOut);提示與錯誤互相取代時兩者同時收合/展開,總高度連續變化。錯誤文字變更時新舊兩則疊在同一格交叉淡接,不重做高度動畫。 - 共用外框:前綴、控制項、後綴放在同一個 flex 容器,以
:focus-within顯示 ring 與較深的邊框;錯誤時改為紅框。純文字前後綴(如"https://"、".example.tw")畫成灰底分段,icon 與按鈕則透明貼齊。 - 聚焦回饋:標籤文字由 neutral-600 變為 neutral-950(深色模式反向),外框下緣一條 2px 線從中心向兩側展開;兩者時間由
--wb-form-field-focus-duration(預設 150ms)控制。外層另以 style 寫入--wb-form-field-duration,值等於duration。 - 字數以
Array.from(value).length計算,中文與 emoji 各算一字;IME 組字中不截斷,組字結束才依maxLength截斷,避免打斷注音/拼音輸入。 - 內建 input 的
onEnter有 IME 組字保護:中文等輸入法組字中按 Enter 不會觸發,並處理 Safari 在compositionend後才發 keydown 的情況。 - render prop 收到的
className是無邊框、透明底的控制項樣式;直接展開{...field}即可與外框融為一體,要追加樣式就自己串接。
可及性
- 標籤以
htmlFor關聯到控制項;aria-describedby依序串起說明、提示或錯誤、字數 - 錯誤時控制項設定
aria-invalid,錯誤區為role="alert",出現與改字都會被螢幕閱讀器朗讀;錯誤以 CircleAlert 圖示加文字呈現,不只靠顏色 required時內建 input 加required與aria-required,render prop 也會收到aria-required;紅色星號對輔助科技隱藏- 聚焦外框以
:focus-within呈現,鍵盤與滑鼠聚焦都看得到 - 使用者系統開啟「減少動態效果」時,錯誤直接出現/消失(只淡入 100ms),底線不展開只變色