Form
- Rerender on demand, avoids unnecessary full-volume rendering, higher performance
- Easy to use, simple structure, avoids unnecessary hierarchical nesting
- Perfect accessibility support
- FormState / FieldState can also be easily obtained from outside the Form Provides an external method to operate inside the form: formApi / fieldApi
- Support for encapsulating custom components into form controls, and you can quickly access your team's components through the extension mechanism provided by Form (through
withField) - Support Form level / Field level assignment, verification (synchronous / asynchronous)
Field
Semi encapsulates all form field components (Input, Select, Checkbox, DatePicker etc.) once.
Taking over their data flow (value & onChange)
When in use, you need to use the components whose names start with Form (note: only these components have data synchronization). They are both named exports and mounted under Form: FormInput and Form.Input are the same component.
Supported Field Component
- Fields:
FormInput,FormInputNumber,FormTextArea,FormSelect,FormCheckbox,FormCheckboxGroup,FormRadio,FormRadioGroup,FormSwitch,FormDatePicker,FormTimePicker,FormSlider,FormTreeSelect,FormCascader,FormTransfer,FormRating,FormAutoComplete,FormUpload,FormPinCode,FormTagInput - Other parts:
FormField,ArrayField,FormInputGroup,FormLabel,FormErrorMessage,FormSection,FormSlot
<script lang="ts">
// A field takes over its data flow when used inside <Form>
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
// A plain Input is left untouched by the Form
import { Input } from '@fibrae/semi-ui-svelte';
</script>
<Form>
<FormInput field="name" />
<Form.Select field="role" optionList={[{ label: 'Developer', value: 'rd' }]} />
</Form>
The members mounted under Form are all field components (Form.Input, Form.Select, Form.Transfer etc., without the Form prefix), plus Form.Slot, Form.ErrorMessage, Form.InputGroup, Form.Label, Form.Section and Form.useForm.
The Field level component provided by Form, its value (or other properties specified by fieldOptions.valueKey) and onChange (or other callbacks specified by onKeyChangeFnName) are taken over by Form, so
Demos
Various ways to declare form
Semi Form supports multiple writing at the same time.
Basic Usage
Use the fields whose names start with Form, such as FormInput and FormSelect, add the field property to each of them, and place them inside the Form. You can also set label properties for each field, by default is the same as field
label can be passed in a string directly, or declared in the form of an object, configure required, optional, align, width and other attributes to deal with more complex scenarios. To place nodes, use snippets: fieldLabel replaces the label content and labelExtra places content right after it (the help icon in the example below); snippets take precedence over the label property
Code
<script lang="ts">
import { IconHelpCircle } from '@fibrae/semi-icons-svelte';
import { Form, FormInput, FormSelect, Tooltip } from '@fibrae/semi-ui-svelte';
const roleOptions = [
{ label: '管理员', value: 'admin' },
{ label: '普通用户', value: 'user' },
{ label: '访客', value: 'guest' },
];
</script>
{#snippet help()}
详情
{/snippet}
<Form layout="horizontal" onValueChange={(values) => console.log(values)}>
<FormInput field="UserName" label="用户名" style="width: 80px" />
<FormInput field="Password" label="密码" style="width: 176px">
{#snippet labelExtra()}
<Tooltip content={help}>
<IconHelpCircle aria-label="帮助" style="color: var(--semi-color-text-2)" />
</Tooltip>
{/snippet}
</FormInput>
<FormSelect field="Role" label={{ text: '角色', optional: true }} optionList={roleOptions} style="width: 176px" />
</Form>Other declaration methods
When you need to get formState, formApi, values, etc. directly inside the Form structure, you can use the following writing
Via the children snippet
The children snippet of Form receives { formState, formApi, values }; destructure what you need with {#snippet children({ formState })}. The render props, child render function and props.component of React all correspond to this one form in Svelte, so they are not distinguished
Code
<script lang="ts">
import { Form, FormInput, FormSelect } from '@fibrae/semi-ui-svelte';
const roleOptions = [
{ label: '管理员', value: 'admin' },
{ label: '普通用户', value: 'user' },
{ label: '访客', value: 'guest' },
];
</script>
<Form layout="horizontal" onValueChange={(values) => console.log(values)}>
{#snippet children({ formState })}
<FormSelect field="Role" label="角色" optionList={roleOptions} style="width: 176px" />
<FormInput field="UserName" label="用户名" style="width: 80px" />
<FormInput field="Password" label="密码" style="width: 176px" />
<code style="margin-top: 24px">{JSON.stringify(formState)}</code>
{/snippet}
</Form>All supported field components
Code
<script lang="ts">
import { IconUpload } from '@fibrae/semi-icons-svelte';
import {
Button,
Col,
Form,
FormCascader,
FormCheckbox,
FormCheckboxGroup,
FormDatePicker,
FormInput,
FormInputNumber,
FormRadio,
FormRadioGroup,
FormRating,
FormSection,
FormSelect,
FormSlider,
FormSwitch,
FormTagInput,
FormTextArea,
FormTimePicker,
FormTreeSelect,
FormUpload,
Row,
Space,
} from '@fibrae/semi-ui-svelte';
const today = new Date(2024, 4, 6, 10, 30, 0);
const initValues = {
name: 'semi',
business: ['ulikeCam'],
role: 'ued',
switch: true,
files: [
{
uid: '1',
name: 'vigo.png',
status: 'success',
size: '130KB',
preview: true,
url: '/assets/placeholder/avatar-d.svg',
},
{
uid: '2',
name: 'resso.jpeg',
status: 'validateFail',
size: '222KB',
percent: 50,
preview: true,
fileInstance: new File([new ArrayBuffer(2048)], 'resso.jpeg', { type: 'image/jpeg' }),
url: '/assets/placeholder/avatar-b.svg',
},
{
uid: '3',
name: 'douyin.jpeg',
status: 'uploading',
size: '222KB',
percent: 50,
preview: true,
fileInstance: new File([new ArrayBuffer(2048)], 'dy.jpeg', { type: 'image/jpeg' }),
url: '/assets/placeholder/avatar-a.svg',
},
],
};
const treeData = [
{
label: '亚洲',
value: 'Asia',
key: '0',
children: [
{
label: '中国',
value: 'China',
key: '0-0',
children: [
{ label: '北京', value: 'Beijing', key: '0-0-0' },
{ label: '上海', value: 'Shanghai', key: '0-0-1' },
],
},
],
},
{
label: '北美洲',
value: 'North America',
key: '1',
},
];
const roleOptions = [
{ label: '运营', value: 'operate' },
{ label: '开发', value: 'rd' },
{ label: '产品', value: 'pm' },
{ label: '设计', value: 'ued' },
];
const businessOptions = [
{ label: 'Semi', value: 'abc' },
{ label: '轻颜相机', value: 'ulikeCam' },
{ label: '今日头条', value: 'toutiao' },
];
const style = 'width: 90%';
</script>
<Form {initValues} onValueChange={(values) => console.log(values)} style="padding: 10px; width: 100%">
<FormSection>
{#snippet text()}基本信息{/snippet}
<Row>
<Col span={12}>
<FormInput field="name" initValue="mikeya" label="名称(Input)" {style} trigger="blur" />
</Col>
<Col span={12}>
<FormDatePicker field="date" initValue={today} label="日期(DatePicker)" placeholder="请选择生效日期" {style} />
</Col>
</Row>
<Row>
<Col span={12}>
<FormSelect field="role" label="角色(Select)" optionList={roleOptions} placeholder="请选择你的角色" {style} />
</Col>
<Col span={12}>
<FormSelect field="business" label="业务线(多选Select)" multiple optionList={businessOptions} placeholder="请选择业务线" {style}>
{#snippet extraText()}
<div style="color: var(--semi-blue-5); font-size: 14px; user-select: none; cursor: pointer">没有找到合适的业务线?</div>
{/snippet}
</FormSelect>
</Col>
</Row>
<Row>
<Col span={12}>
<FormCascader field="area" label="地区(Cascader)" placeholder="请选择所在地区" {style} {treeData} />
</Col>
<Col span={12}>
<FormTreeSelect field="tree" filterTreeNode label="节点(TreeSelect)" placeholder="请选择服务节点" {style} {treeData} />
</Col>
</Row>
<Row>
<Col span={12}>
<FormTagInput field="product" initValue={['abc', 'ulikeCam']} label="产品(TagInput)" placeholder="请输入产品" {style} />
</Col>
</Row>
<Row>
<Col span={24}>
<FormUpload action="//semi.design/api/upload" field="files" label="证明文件(Upload)">
<Button theme="light">
{#snippet icon()}
<IconUpload />
{/snippet}
点击上传
</Button>
</FormUpload>
</Col>
</Row>
</FormSection>
<FormSection>
{#snippet text()}资源详情{/snippet}
<Row>
<Col span={12}>
<FormTextArea field="description" label="申请理由(TextArea)" placeholder="请填写申请资源理由" style="width: 90%; height: 120px" />
</Col>
<Col span={12}>
<FormCheckboxGroup direction="horizontal" field="type" initValue={['user', 'admin']} label="申请类型(CheckboxGroup)" rules={[{ required: true }]}>
<FormCheckbox value="admin">admin</FormCheckbox>
<FormCheckbox value="user">user</FormCheckbox>
<FormCheckbox value="guest">guest</FormCheckbox>
<FormCheckbox value="root">root</FormCheckbox>
</FormCheckboxGroup>
<FormRadioGroup field="isMonopolize" label="是否独占资源(Radio)" rules={[{ type: 'boolean' }, { required: true, message: '必须选择是否独占 ' }]}>
<FormRadio value={1}>是</FormRadio>
<FormRadio value={0}>否</FormRadio>
</FormRadioGroup>
</Col>
</Row>
<Row>
<Col span={12}>
<FormTimePicker field="time" label="截止时刻(TimePicker)" {style} />
</Col>
<Col span={12}>
<FormInputNumber field="number" initValue={20} label="申请数量(InputNumber)" {style} />
</Col>
</Row>
<Row>
<Col span={12}>
<FormSlider field="range" initValue={10} label="资源使用报警阈值(%)(Slider)" {style} />
</Col>
<Col span={12}>
<FormSwitch field="switch" label="开关(Switch)" />
</Col>
</Row>
<Row>
<Col span={12}>
<FormRating field="rating" initValue={2} label="满意度(Rating)" {style} />
</Col>
</Row>
</FormSection>
<FormCheckbox field="agree" noLabel value="false">我已阅读并清楚相关规定(Checkbox)</FormCheckbox>
<Space>
<Button class="btn-margin-right" htmlType="submit" type="primary">提交(submit)</Button>
<Button htmlType="reset">重置(reset)</Button>
</Space>
</Form>Transfer field
The value of a Transfer field is the array of the selected items' values. Its initial value, validation and reset work like any other field. With blur in trigger, validation runs and the field is marked touched only once the focus leaves the whole Transfer, not when it moves between the search box and the lists.
Code
<script lang="ts">
import { Form, FormTransfer } from '@fibrae/semi-ui-svelte';
const dataSource = Array.from({ length: 6 }, (_, index) => ({ label: `成员 ${index + 1}`, value: index + 1, key: index + 1 }));
</script>
<Form initValues={{ members: [1, 3] }}>
<FormTransfer {dataSource} field="members" label="项目成员" rules={[{ required: true, type: 'array', message: '请至少选择一名成员' }]} style="width: 480px; height: 320px" />
</Form>Field binding syntax
Every Field component must have a field property. This is how the form manages the state of this field. See the field syntax section below for additional details on what you can pass in for field.
The field can be a simple string, can be contained.Or[]String that supports multi-level nesting
Below is an example of the field name and their mapping path in FormState
| Field | Resolution |
|---|---|
| username | formState.values.username |
| user[0] | formState.values.user[0] |
| siblings.1 | formState.values.siblings[1] |
| siblings['2'] | formState.values.siblings[2] |
| parents[0].name | formState.values.parents[0].name |
| parents[1]['name'] | formState.values.parents[1].name |
Code
<script lang="ts">
import { Col, Form, FormInput, FormLabel, Row, TextArea, Toast } from '@fibrae/semi-ui-svelte';
const onSubmit = (values: Record<string, unknown>) => Toast.info({ content: JSON.stringify(values) });
</script>
<Form {onSubmit}>
{#snippet children({ formState })}
<Row>
<Col span={12}>
<FormInput aria-label="username" field="username" placeholder="请尝试输入值" />
<FormInput aria-label="user[0]" field="user[0]" placeholder="请尝试输入值" />
<FormInput aria-label="siblings.1" field="siblings.1" placeholder="请尝试输入值" />
<FormInput aria-label="siblings['2']" field="siblings['2']" placeholder="请尝试输入值" />
<FormInput aria-label="parents[0].name" field="parents[0].name" placeholder="请尝试输入值" />
<FormInput aria-label="parents[1]['name']" field="parents[1]['name']" placeholder="请尝试输入值" />
</Col>
<Col offset={1} span={10} style="margin-top: 12px">
<FormLabel text="FormState实时映射值:" />
<TextArea aria-label="FormState实时映射值" value={JSON.stringify(formState.values)} />
</Col>
</Row>
{/snippet}
</Form>Form layout
- Vertical Layout: Arrange each field vertically (By default)
Semi Design Next recommends a vertical layout.
Code
<script lang="ts">
import { Button, Form, FormCheckbox, FormInput, Toast } from '@fibrae/semi-ui-svelte';
const handleSubmit = (values: Record<string, unknown>) => {
console.log(values);
Toast.info('表单已提交');
};
</script>
<Form onSubmit={handleSubmit} style="width: 400px">
{#snippet children({ values })}
<FormInput field="phone" label="PhoneNumber" placeholder="Enter your phone number" style="width: 100%" />
<FormInput field="password" label="Password" placeholder="Enter your password" style="width: 100%" />
<FormCheckbox field="agree" noLabel>I have read and agree to the terms of service</FormCheckbox>
<div style="display: flex; justify-content: space-between; align-items: center">
<p>
<span>Or</span>
<Button style="color: var(--semi-color-primary); margin-left: 10px; cursor: pointer" theme="borderless">Sign up</Button>
</p>
<Button disabled={!values.agree} htmlType="submit" type="tertiary">Log in</Button>
</div>
{/snippet}
</Form>- Horizontal Layout: Arrange each field horizontally You can use the horizontal layout by setting
layout='horizontal'
Code
<script lang="ts">
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
</script>
<Form layout="horizontal">
<FormInput field="phone" label="PhoneNumber" placeholder="Enter your phone number" />
<FormInput field="password" label="Password" placeholder="Enter your password" />
</Form>- Label Position, Label Align
You can control the position of the label in the Field and the direction of text alignment by settinglabelPosition,labelAlign
Code
<script lang="ts">
import { Form, FormCheckbox, FormCheckboxGroup, FormInput, FormInputNumber, FormLabel, FormRadio, FormRadioGroup, FormSelect, FormSwitch, Select, type FormProps, type RuleItem } from '@fibrae/semi-ui-svelte';
let labelPosition = $state<NonNullable<FormProps['labelPosition']>>('left');
let labelAlign = $state<NonNullable<FormProps['labelAlign']>>('left');
const labelWidth = $derived(labelPosition === 'left' ? '180px' : 'auto');
const rules: RuleItem[] = [
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (_rule, value) => value === 'semi', message: 'should be semi' },
];
const positionOptions = [
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
];
const alignOptions = [
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
];
const nameOptions = [
{ label: 'mike', value: 'mike' },
{ label: 'jane', value: 'jane' },
{ label: 'kate', value: 'kate' },
];
</script>
<div style="border-bottom: 1px solid var(--semi-color-border); padding-bottom: 12px">
<FormLabel style="margin-left: 10px">切换Label位置:</FormLabel>
<Select aria-label="labelPosition" bind:value={() => labelPosition, (next) => (labelPosition = next as typeof labelPosition)} optionList={positionOptions} style="width: 200px">
{#snippet prefix()}labelPosition{/snippet}
</Select>
<FormLabel style="margin-left: 10px">切换Label文本对齐方向:</FormLabel>
<Select aria-label="labelAlign" bind:value={() => labelAlign, (next) => (labelAlign = next as typeof labelAlign)} optionList={alignOptions} style="width: 200px">
{#snippet prefix()}labelAlign{/snippet}
</Select>
</div>
{#key `${labelPosition}${labelAlign}`}
<Form {labelAlign} {labelPosition} {labelWidth} style="padding: 10px; width: 600px">
<FormInput field="input" label="手机号码" {rules} style="width: 200px" trigger="blur" />
<FormSwitch field="agree" label="是否同意" />
<FormInputNumber field="price" label="价格" style="width: 200px" />
<FormSelect field="name" label="姓名" optionList={nameOptions} style="width: 200px" />
<FormCheckboxGroup direction="horizontal" field="role" label="角色">
<FormCheckbox value="admin">admin</FormCheckbox>
<FormCheckbox value="user">user</FormCheckbox>
<FormCheckbox value="guest">guest</FormCheckbox>
<FormCheckbox value="root">root</FormCheckbox>
</FormCheckboxGroup>
<FormRadioGroup field="性别">
<FormRadio value="1">man</FormRadio>
<FormRadio value="2">woman</FormRadio>
</FormRadioGroup>
</Form>
{/key}- A more complex layout.
You can also combine theRowandColprovided by theGridto arrange the form structure as you want.
Code
<script lang="ts">
import { Col, Form, FormDatePicker, FormInput, FormSelect, Row, type RuleItem } from '@fibrae/semi-ui-svelte';
const nickNameRules: RuleItem[] = [
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (_rule, value) => value === 'semi', message: 'should be semi' },
];
const businessOptions = [
{ label: 'Semi', value: 'abc' },
{ label: '轻颜相机', value: 'ulikeCam' },
{ label: '今日头条', value: 'toutiao' },
];
const roleOptions = [
{ label: '运营', value: 'operate' },
{ label: '开发', value: 'rd' },
{ label: '产品', value: 'pm' },
{ label: '设计', value: 'ued' },
];
</script>
<Form labelPosition="top" style="padding: 10px">
<Row>
<Col span={8}>
<FormInput field="nickName1" label="用户名" rules={nickNameRules} style="width: 250px" trigger="blur" />
</Col>
<Col span={8}>
<FormDatePicker field="date1" label="有效日期" style="width: 250px" />
</Col>
<Col span={8}>
<FormSelect field="business1" label="业务线" optionList={businessOptions} style="width: 250px" />
</Col>
</Row>
<Row>
<Col span={6}>
<FormInput field="nickName2" label="用户名" rules={nickNameRules} style="width: 200px" trigger="blur" />
</Col>
<Col span={6}>
<FormDatePicker field="date2" label="有效日期" style="width: 200px" />
</Col>
<Col span={6}>
<FormSelect field="business2" label="业务线" optionList={businessOptions} style="width: 200px" />
</Col>
<Col span={6}>
<FormSelect field="role" label="角色" optionList={roleOptions} style="width: 200px" />
</Col>
</Row>
</Form>Form grouping
Forms with many fields should group them. Use FormSection to group Fields and put the title in the text snippet (it only affects layout and does not change the data structure).
Code
<script lang="ts">
import { Button, Form, FormCheckbox, FormDatePicker, FormInput, FormInputNumber, FormRadio, FormRadioGroup, FormSection, FormSelect, FormSwitch, Space } from '@fibrae/semi-ui-svelte';
const today = new Date(2024, 4, 6, 10, 30, 0);
const userOptions = [
{ label: '曲晨一', value: '1' },
{ label: '夏可曼', value: '2' },
{ label: '曲晨三', value: '3' },
{ label: '蔡妍', value: '4' },
];
</script>
<Form style="width: 560px">
<FormSection>
{#snippet text()}基本信息{/snippet}
<FormInput field="name" initValue="TCS任务平台使用" label="考试名称" style="width: 560px" />
</FormSection>
<FormSection>
{#snippet text()}合格标准{/snippet}
<div style="display: flex">
<FormInputNumber field="pass" initValue={60} label={{ text: '及格正确率', required: true }} style="width: 80px" />
<FormInputNumber field="number" initValue={10} label={{ text: '合格人数', required: true }} style="width: 80px" />
</div>
</FormSection>
<FormSection>
{#snippet text()}考试时间{/snippet}
<FormDatePicker field="date" initValue={today} label={{ text: '开始时间', required: true }} style="width: 272px" type="dateTime" />
<div style="display: flex">
<FormInput field="time" initValue="60" label="考试时长" style="width: 176px">
{#snippet addonAfter()}分钟{/snippet}
</FormInput>
<FormCheckbox field="auto" initValue noLabel style="padding-top: 30px; margin-inline-start: 12px">到时间自动交卷</FormCheckbox>
</div>
<FormRadioGroup direction="vertical" field="type" initValue="always" label="有效时间">
<FormRadio value="always">永久有效</FormRadio>
<FormRadio value="user">自定义有效期</FormRadio>
</FormRadioGroup>
<FormRadioGroup direction="vertical" field="answerTime" initValue="always" label="答案放出时间" rules={[{ required: true }]}>
<FormRadio value="always">自动放出</FormRadio>
<FormRadio value="user">
<div style="display: inline-block">
自定义放出时间
<FormDatePicker field="customTime" noLabel style="width: 464px; display: inline-block" type="dateTimeRange" />
</div>
</FormRadio>
</FormRadioGroup>
</FormSection>
<FormSection>
{#snippet text()}考试人员{/snippet}
<div style="display: flex">
<FormSwitch field="open" label={{ text: '对外开放', required: true }}>
{#snippet checkedText()}开{/snippet}
{#snippet uncheckedText()}关{/snippet}
</FormSwitch>
</div>
<FormSelect field="users" initValue={['1', '2', '3', '4']} label={{ text: '考生', required: true }} multiple optionList={userOptions} style="width: 560px" />
</FormSection>
<Space>
<Button style="width: 120px; margin-top: 12px; margin-inline-end: 4px" theme="solid" type="primary">创建考试</Button>
<Button style="margin-top: 12px">预览</Button>
</Space>
</Form>wrapper Col / label Col
When you need to set a uniform layout for all Fields in a Form, you can set wrapperCol and labelCol on the Form to quickly generate the layout. No need to manually use Row, Col manual layout.
wrapperCol,labelColProperty Configuration Reference Col components
Code
<script lang="ts">
import { Form, FormInput, FormSelect } from '@fibrae/semi-ui-svelte';
const roleOptions = [
{ label: '运营', value: 'operate' },
{ label: '开发', value: 'rd' },
{ label: '产品', value: 'pm' },
{ label: '设计', value: 'ued' },
];
</script>
<Form labelAlign="right" labelCol={{ span: 2 }} labelPosition="left" wrapperCol={{ span: 20 }}>
<FormInput field="name" label="姓名" placeholder="请输入姓名" style="width: 250px" trigger="blur" />
<FormSelect field="role" label="角色" optionList={roleOptions} placeholder="请选择角色" style="width: 250px" />
</Form>Remove automatically added Label
Form will automatically insert Label for Field control. If you don't need to automatically insert the Label module, you can turn off the automatic label insertion function by setting noLabel=true in the Field (at this time, the Field still has the ability to automatically display ErrorMessage, so the DOM structure is still different from the original component)
If you want to keep the DOM structure consistent with the original component, you can use pure=true. At this time, the DOM structure will not change except that the data flow is taken over (you need to be responsible for the rendering of ErrorMessage, and it cannot be used by formProps.wrapperCol property impact)
Code
<script lang="ts">
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
</script>
<Form onSubmit={(values) => console.log(values)} style="width: 400px">
<FormInput field="name" label="姓名" noLabel placeholder="请输入姓名" style="width: 250px" trigger="blur" />
<FormInput field="purename" placeholder="DOM结构与普通 Input 组件完全一致" pure />
</Form>Embedded Label
A Label can be inlined in a field control by setting labelPosition to inset. Components currently supporting this feature include Input, InputNumber, DatePicker, TimePicker, Select, TreeSelect, Cascader, TagInput, AutoComplete. With labelPosition inset, the other fields are laid out as top and warn in development mode.
A custom component wrapped with FormField is laid out with an inset label when fieldOptions.insetLabel is true (the default): it receives the insetLabel and insetLabelId props, has to render the inset label itself and write insetLabelId to the element that holds the label. A component that does not render an inset label sets insetLabel to false and is then laid out as top.
Code
<script lang="ts">
import { Form, FormDatePicker, FormInput, FormSelect } from '@fibrae/semi-ui-svelte';
const today = new Date(2024, 4, 6, 10, 30, 0);
const roleOptions = [
{ label: '运营', value: 'operate' },
{ label: '开发', value: 'rd' },
{ label: '产品', value: 'pm' },
{ label: '设计', value: 'ued' },
];
</script>
<Form labelPosition="inset" layout="horizontal">
<FormInput field="name" initValue="semi" label="姓名" placeholder="请输入姓名" style="width: 250px" trigger="blur" />
<FormSelect field="role" initValue="rd" label="角色" optionList={roleOptions} style="width: 250px" />
<FormDatePicker field="date" initValue={today} label="开始日期" style="width: 250px" />
</Form>Export Label, ErrorMessage use
When the built-in Label and ErrorMessage layout does not meet the business requirements, you need to combine the positions yourself, but you want to use the default styles of Label and ErrorMessage directly.
you can import FormLabel / FormErrorMessage and combine them by yourself.
For details of their API, refer to FormLabel / FormErrorMessage
import { FormErrorMessage, FormLabel } from '@fibrae/semi-ui-svelte';
Use FormSlot
When your custom component needs to maintain the same layout style as the Field component, you can place your custom component in FormSlot
labelWidth, labelAlign, wrapperCol, labelCol set on the Form component automatically acts on FormSlot
For the FormSlot property configuration, refer to FormSlot
Code
<script lang="ts">
import { Form, FormInput, FormSlot } from '@fibrae/semi-ui-svelte';
const log = (value: unknown) => console.log(value);
</script>
<Form labelPosition="left" labelWidth={100} onChange={log} onSubmit={log} style="width: 600px">
<FormInput field="特效名称" style="width: 250px" />
<FormSlot error="我是SlotA的ErrorMessage" label={{ text: 'SlotA' }}>
<div style="display: flex; align-items: center; height: 32px; margin-top: 8px">我是Semi Form SlotA, 我是自定义的节点</div>
</FormSlot>
<FormSlot label={{ text: 'SlotB', width: 160, align: 'right' }}>
<div style="display: flex; align-items: center; height: 100%">我是Semi Form SlotB, 我的Label Align、Width与众不同</div>
</FormSlot>
</Form>Use helpText、extraText set prompt information
You can place custom prompt information through the helpText snippet, and display it in the same block as the verification information (error). When both have values, the verification information will be displayed first.
Additional prompt information can be placed through the extraText snippet. When the error message and prompt text need to appear at the same time, this snippet can be used. It is always displayed and located after helpText/error
When validateStatus is passed in, the UI style corresponding to the value of validateStatus will be displayed first. If not passed in, the internal verification status of the field shall prevail.
Code
<script lang="ts">
import { Form, FormInput, type FormApi } from '@fibrae/semi-ui-svelte';
type ValidateStatus = 'default' | 'error' | 'warning' | 'success';
let hint = $state('');
let validateStatus = $state<ValidateStatus>('default');
let formApi: FormApi | undefined;
const validator = (val?: string) => {
if (!val) {
validateStatus = 'error';
return '密码不能为空';
} else if (val.length <= 3) {
validateStatus = 'warning';
hint = '密码强度:弱'; // 展示 helpText
return ''; // 校验通过
}
hint = '';
validateStatus = 'success';
return '';
};
const onSubmit = () => console.log('submit success');
const onSubmitFail = (errors: unknown) => console.log(errors);
const random = () => {
const pw = (Math.random() * 100000).toString().slice(0, 5);
formApi?.setValue('Password', pw);
formApi?.setError('Password', '');
hint = '';
validateStatus = 'success';
};
</script>
{#snippet weak()}
<span style="color: var(--semi-color-warning)">{hint}</span>
{/snippet}
{#snippet generate()}
<button style="padding: 0; border: none; background: none; font: inherit; color: var(--semi-color-link); font-size: 14px; user-select: none; cursor: pointer" type="button" onclick={random}> 没有想到合适的密码?点击随机生成一个 </button>
{/snippet}
<Form getFormApi={(api) => (formApi = api)} {onSubmit} {onSubmitFail} showValidateIcon>
<FormInput extraText={generate} field="Password" helpText={hint ? weak : undefined} {validateStatus} {validator} />
</Form>By configuring extraTextPosition, you can control the display position of extraText. Optional values bottom, middle
For example, when you want to display the extraText prompt information between the Label and Field component.
This attribute can be configured uniformly on the Form or individually on each Field. When passing in at the same time, the configuration of the Field shall prevail.
Code
<script lang="ts">
import { Form, FormCheckboxGroup, FormInputNumber } from '@fibrae/semi-ui-svelte';
const options = [
{ label: '飞书通知', value: 'lark' },
{ label: '邮件通知', value: 'email' },
{ label: '顶部横幅通知', value: 'notification' },
];
</script>
<Form extraTextPosition="middle">
<FormCheckboxGroup direction="horizontal" field="notify" label="通知方式" {options}>
{#snippet extraText()}
未勾选时,默认为红点提醒,消息默认进入收件人消息列表。对于重要通知,可同时勾选相应的通知方式。
{/snippet}
</FormCheckboxGroup>
<FormInputNumber extraTextPosition="bottom" field="force" label="强制读取(可选)" placeholder="秒">
{#snippet extraText()}
对于对话框通知,可指定该消息必须在指定时长后才可置为已读。
{/snippet}
</FormInputNumber>
</Form>Using Input Group
When you need to combine some fields to use, you can use FormInputGroup to wrap them. Only the direct children that set field are rendered inside the group.
When you add the field property to fields such as FormSelect and FormInput, Form automatically inserts a Label for each of them by default.
But usually, in FormInputGroup you only need a Label belonging to the entire Group. You can set the label property in the FormInputGroup to insert a Label belonging to the Group
label configurable properties, see FormLabel
Code
<script lang="ts">
import { Button, Form, FormInput, FormInputGroup, FormSelect } from '@fibrae/semi-ui-svelte';
const onSubmit = (values: unknown) => console.log(values);
const prefixOptions = [
{ label: '美国+1', value: '+1' },
{ label: '香港+852', value: '+852' },
{ label: '中国+86', value: '+86' },
{ label: '日本+81', value: '+81' },
];
</script>
<Form labelPosition="top" {onSubmit} style="width: 400px">
<FormInputGroup label={{ text: '手机号码', required: true }} labelPosition="top">
<FormSelect field="phonePrefix" initValue="+86" optionList={prefixOptions} rules={[{ required: true }]} showClear style="width: 150px" />
<FormInput field="phoneNumber" initValue="18912345678" rules={[{ required: true }]} showClear style="width: 250px" />
</FormInputGroup>
<FormInput field="姓名" initValue="Semi" trigger="blur" />
<Button htmlType="submit">提交</Button>
</Form>Form in the Modal pop-up layer
You can place the Form in Modal and load it as a popup.
When submitting, use formApi.validate() to centrally verify the Field
Code
<script lang="ts">
import { Button, Col, Form, FormInput, FormSelect, Modal, Row, type FormApi } from '@fibrae/semi-ui-svelte';
const message = '该项为必填项';
const rules = [{ required: true, message }];
const regions = [
{ value: 'China', label: '中国' },
{ value: 'US', label: '美国' },
{ value: 'Europe', label: '欧洲' },
{ value: 'Japan', label: '日本' },
];
let visible = $state(false);
let formApi: FormApi | undefined;
const handleOk = () => {
formApi
?.validate()
.then((values) => {
console.log(values);
})
.catch((errors) => {
console.log(errors);
});
};
</script>
<Button onclick={() => (visible = true)}>打开弹窗</Button>
<Modal bind:visible onOk={handleOk} style="width: 600px" title="新建">
<Form getFormApi={(api) => (formApi = api)}>
<Row>
<Col span={5}>
<FormSelect field="region" label="国家/地区" optionList={regions} placeholder="请选择" {rules} style="width: 100%" />
</Col>
<Col offset={2} span={15}>
<FormInput field="owner" label="业务执行人" {rules} trigger="blur" />
</Col>
</Row>
<Row>
<Col span={5}>
<FormSelect field="area" label="投放区域" optionList={regions} placeholder="请选择" {rules} style="width: 100%" />
</Col>
<Col offset={2} span={15}>
<FormInput field="department" label="业务执行部门" {rules} trigger="blur" />
</Col>
</Row>
</Form>
</Modal>Configure initial values and verification rules
- You can configure check rules for each Field through
rules
The verification library inside the Form is based onasync-validator, and more configuration rules can be found in its official documentation - You can uniformly set the initial value for the entire form through the
initValuesof form, or you can set the initial value throughinitValuein each field (the latter has a higher priority) - You can configure different verification trigger timings for each Field through
trigger, and the default ischange(that is, the verification is performed automatically when the value changes). Also supportschange,blur,mount,customor a combination of the above. It also supports unified configuration through FormProps. If both are configured, FieldProps shall prevail - You can use the
stopValidateWithErrorswitch to decide whether to continue to trigger the validation of subsequent rules when the first rule that fails the validation is encountered. Unified configuration through FormProps is also supported. If both are configured, FieldProps shall prevail
Code
<script lang="ts">
import { Button, Form, FormInput, type RuleItem } from '@fibrae/semi-ui-svelte';
const initValues = {
name: 'semi',
shortcut: 'se',
};
const style = 'width: 100%';
const rules: RuleItem[] = [
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (_rule, value) => value === 'semi', message: 'should be semi' },
{
validator: (_rule, value) => Boolean(value && value.startsWith('se')),
message: 'should startsWith se',
},
];
</script>
<Form {initValues}>
<FormInput field="name" {rules} {style} trigger="blur" />
<FormInput field="shortcut" {rules} stopValidateWithError {style} />
<Button htmlType="submit">提交</Button>
</Form>Custom Validate (Form Level)
You can set a custom validation function for the Form as a whole with validator. It will be called on submit or when calling formApi.validate().
Synchronous Validate
When validate success, you should return an empty string.
When validate fails, you should return the error message (Object, key is fieldName, value is the corresponding error message)
Code
<script lang="ts">
import { Button, Form, FormInput } from '@fibrae/semi-ui-svelte';
interface FormValues {
name?: string;
sex?: string;
}
const syncValidate = (values: FormValues) => {
const errors: Record<string, unknown> = {};
if (values.name !== 'mike') {
errors.name = 'you must name mike';
}
if (values.sex !== 'female') {
errors.sex = 'must be woman';
}
errors.familyName = [{ before: 'before error balabala ', after: 'after error balabala' }, 'familyName[1] error balabala'];
return errors;
};
</script>
<Form layout="horizontal" validator={syncValidate}>
<FormInput field="name" trigger="blur" />
<FormInput field="familyName[0].before" trigger="blur" />
<FormInput field="familyName[0].after" trigger="blur" />
<FormInput field="familyName[1]" trigger="blur" />
<div style="display: flex; align-items: flex-end">
<Button class="btn-margin-right" htmlType="submit" type="primary">Submit</Button>
<Button htmlType="reset">reset</Button>
</div>
</Form>Asynchronous Validate
For asynchronous validation, you should return a promise. In promise.then() you need to return the corresponding error message.
Code
<script lang="ts">
import { Button, Form, FormInput } from '@fibrae/semi-ui-svelte';
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
interface FormValues {
name?: string;
sex?: string;
}
const asyncValidate = (values: FormValues) =>
sleep(2000).then(() => {
const errors: FormValues = {};
if (values.name !== 'mike') {
errors.name = 'you must name mike';
}
if (values.sex !== 'female') {
errors.sex = 'sex not valid';
}
return errors;
});
</script>
<Form layout="horizontal" validator={asyncValidate}>
<FormInput field="name" trigger="blur" />
<FormInput field="familyName[0].before" trigger="blur" />
<FormInput field="familyName[1]" trigger="blur" />
<FormInput field="sex" trigger="blur" />
<div style="display: flex; align-items: flex-end">
<Button class="btn-margin-right" htmlType="submit" type="primary">Submit</Button>
<Button htmlType="reset">reset</Button>
</div>
</Form>Custom Validate (Field Level)
You can specify a custom validation function for a single Field with validator. Supports synchronous and asynchronous validation (by returning promises)
Code
<script lang="ts">
import { Button, Form, FormInput } from '@fibrae/semi-ui-svelte';
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const validateName = (val?: string) => {
if (!val) {
return "【sync】can't be empty";
} else if (val.length <= 5) {
return '【sync】must more than 5';
}
return '';
};
const asyncValidate = (val?: string) =>
sleep(2000).then(() => {
if (!val) {
return "【async】can't be empty";
} else if (val.length <= 5) {
return '【async】must more than 5';
} else {
return '';
}
});
</script>
<Form>
<FormInput field="name" label="【name】asyncValidate after 2s" trigger="blur" validator={asyncValidate} />
<FormInput field="familyName" label="【familyName】syncValidate" trigger="blur" validator={validateName} />
<Button htmlType="reset">reset</Button>
</Form>Manually Trigger specified validation
When you want to manually trigger the validation of some specific Field, you can do it through formApi.validate.
When no parameters are passed in, all Fields are checked by default. When parameters are passed in, the parameters specified shall prevail
Silent Validation
When you need to get the validation result without triggering UI updates (no error messages shown, no touched state set), you can use formApi.validate({ silent: true }). This is useful in scenarios where you want to decide whether to make a backend request based on the validation result.
Code
<script lang="ts">
import { Button, Form, FormInput, Toast, type FormApi } from '@fibrae/semi-ui-svelte';
let formApi: FormApi | undefined;
const usernameRules = [
{ required: true, message: '用户名不能为空' },
{ min: 3, message: '用户名至少 3 个字符' },
];
const emailRules = [
{ required: true, message: '邮箱不能为空' },
{ type: 'email' as const, message: '邮箱格式不正确' },
];
const handleSilentValidate = () => {
formApi
?.validate({ silent: true })
.then((values) => {
Toast.success('表单校验通过,准备发起请求');
console.log('发起后端请求:', values);
})
.catch((errors) => {
Toast.error('表单校验未通过,但不显示错误提示');
console.log('校验错误:', errors);
});
};
const handleNormalValidate = () => {
formApi
?.validate()
.then(() => {
Toast.success('表单校验通过');
})
.catch(() => {
Toast.error('表单校验未通过,显示错误提示');
});
};
</script>
<Form getFormApi={(api) => (formApi = api)} layout="horizontal">
<FormInput field="username" label="用户名" rules={usernameRules} />
<FormInput field="email" label="邮箱" rules={emailRules} />
<div style="display: flex; gap: 12px; margin-top: 12px">
<Button type="primary" onclick={handleSilentValidate}>静默校验(不显示错误)</Button>
<Button onclick={handleNormalValidate}>普通校验(显示错误)</Button>
</div>
</Form>Code
<script lang="ts">
import { Button, Form, FormCheckboxGroup, FormInput, Space, Toast, type FormApi } from '@fibrae/semi-ui-svelte';
const allFields = ['a', 'b', 'c', 'd', 'b.name'];
const options = allFields.map((item) => ({ label: item, value: item }));
const validate = (val?: string) => {
if (!val) {
return "can't be empty";
} else if (val.length <= 5) {
return '我是传入的节点';
}
return undefined;
};
let formApi: FormApi | undefined;
const validatePartial = (type?: 'all') => {
const scope = type === 'all' ? allFields : (formApi?.getValue('validateScope') ?? []);
formApi
?.validate(scope)
.then((values) => {
console.log(values);
Toast.success('pass');
})
.catch((error) => {
Toast.error('error');
console.log(error);
});
};
const resetPartial = () => {
formApi?.reset(formApi.getValue('resetScope'));
};
</script>
<Form autoScrollToError getFormApi={(api) => (formApi = api)} layout="horizontal">
{#snippet children({ values })}
<div>
<FormInput field="a[1]" trigger="blur" validator={validate} />
<FormInput field="a[0]" trigger="blur" validator={validate} />
<FormInput field="b.name[0]" trigger="blur" validator={validate} />
<FormInput field="b.name[1]" trigger="blur" validator={validate} />
<FormInput field="b.type" trigger="blur" validator={validate} />
<FormInput field="c" trigger="blur" validator={validate} />
<FormInput field="d" trigger="blur" validator={validate} />
</div>
<div>
<FormCheckboxGroup direction="horizontal" field="validateScope" initValue={['a', 'b']} label="当前希望校验的Field" {options} />
<FormCheckboxGroup direction="horizontal" field="resetScope" label="当前需要Reset的Field" {options} />
<Space>
<Button htmlType="reset">reset</Button>
<Button onclick={() => validatePartial('all')}>all validate</Button>
<Button onclick={() => validatePartial()}>partial validate {JSON.stringify(values.validateScope)}</Button>
<Button onclick={resetPartial}>partial reset</Button>
</Space>
</div>
{/snippet}
</Form>Linkage Fields
You can achieve the linkage between Fields by listening to the onChange of Field and then using formApi (for example saved through getFormApi) to make modifications.
Code
<script lang="ts">
import { Button, Form, FormInput, FormSelect, Row, type FormApi } from '@fibrae/semi-ui-svelte';
let formApi: FormApi | undefined;
const sexOptions = [
{ label: 'female', value: 'female' },
{ label: 'male', value: 'male' },
];
const handleSelectChange = (value: unknown) => {
const text = value === 'male' ? 'Hi male' : 'Hi female!';
formApi?.setValue('Note', text);
};
</script>
<Form getFormApi={(api) => (formApi = api)} onValueChange={(values) => console.log(values)} style="width: 250px">
<span>Note will change after Sex select</span>
<FormInput field="Note" style="width: 250px" />
<FormSelect field="Sex" onChange={handleSelectChange} optionList={sexOptions} style="width: 250px" />
<Row>
<Button class="btn-margin-right" htmlType="submit" type="primary">Submit</Button>
<Button htmlType="reset">reset</Button>
</Row>
</Form>Dynamic form
Dynamically add and delete fields
Code
<script lang="ts">
import { Button, Form, FormInput, FormRadio, FormRadioGroup } from '@fibrae/semi-ui-svelte';
</script>
<Form style="width: 450px">
{#snippet children({ formState })}
<FormInput field="name" label="用户名称:" />
<FormRadioGroup field="isAnchor" label="是否已注册主播">
<FormRadio value="yes">yes</FormRadio>
<FormRadio value="no">no</FormRadio>
</FormRadioGroup>
{#if formState.values?.isAnchor === 'yes'}
<FormInput field="liveRoom" label="直播间名称" />
{/if}
<Button htmlType="submit">提交</Button>
{/snippet}
</Form>Keep empty values (allowEmpty)
By default a field whose value becomes empty is removed from values. Set allowEmpty on a Field to keep its key with an undefined value, or on Form to apply it to every field. Clear both selects below and compare the keys of values.
Code
<script lang="ts">
import { Form, FormSelect } from '@fibrae/semi-ui-svelte';
const roleOptions = [
{ label: '管理员', value: 'admin' },
{ label: '访客', value: 'guest' },
];
</script>
<Form initValues={{ keep: 'admin', drop: 'admin' }}>
{#snippet children({ values })}
<FormSelect allowEmpty field="keep" label="设置了 allowEmpty" optionList={roleOptions} showClear style="width: 176px" />
<FormSelect field="drop" label="未设置" optionList={roleOptions} showClear style="width: 176px" />
<code>{JSON.stringify(Object.keys(values))}</code>
{/snippet}
</Form>Keep field state on unmount (keepState)
By default, when a Field component is unmounted, its value, error, and touched state are all reset. If you want to preserve these states after the field is unmounted (for example, in conditional rendering scenarios), you can use the keepState property.
Code
<script lang="ts">
import { Button, Form, FormInput, FormSwitch, Switch } from '@fibrae/semi-ui-svelte';
let show = $state(true);
</script>
<Form style="width: 450px">
<FormSwitch field="alwaysShow" label="始终显示的字段" />
<Switch aria-labelledby="form-keep-state-toggle" bind:checked={show} style="margin-bottom: 12px" />
<span id="form-keep-state-toggle" style="margin-left: 8px; color: var(--semi-color-text-2)"> 显示条件字段 </span>
{#if show}
<FormInput field="conditionalField" keepState label="条件字段(keepState)" />
<FormInput field="noKeepState" label="条件字段(无 keepState)" />
{/if}
<Button htmlType="submit">提交</Button>
</Form>In the example above:
- The field with
keepStatewill retain its previously entered value, validation state, etc. after being hidden and then shown again - The field without
keepStatewill have its state reset after being hidden and then shown again
ArrayField Usage
For array items that are dynamically added or deleted, we provide the ArrayField component to simplify the operation of adding and removing rows. The children snippet of ArrayField provides onAdd and onAddWithInitValue to add a blank row or a row with an initial value, and the onRemove of each row removes that row.
For the detailed API of ArrayField, please refer to ArrayField below
Note: The initValue type of ArrayField must be an array
Code
<script lang="ts">
import { IconMinusCircle, IconPlusCircle } from '@fibrae/semi-icons-svelte';
import { ArrayField, Button, Form, FormInput, FormSelect, TextArea } from '@fibrae/semi-ui-svelte';
const data = [
{ name: 'Semi D2C', role: 'Engineer' },
{ name: 'Semi C2D', role: 'Designer' },
];
const roleOptions = [
{ label: 'Engineer', value: 'Engineer' },
{ label: 'Designer', value: 'Designer' },
];
</script>
{#snippet plus()}
<IconPlusCircle />
{/snippet}
{#snippet minus()}
<IconMinusCircle />
{/snippet}
<Form allowEmpty labelPosition="left" labelWidth="100px" style="width: 800px">
{#snippet children({ formState })}
<ArrayField field="rules" initValue={data}>
{#snippet children({ arrayFields, onAdd, onAddWithInitValue })}
<Button icon={plus} theme="light" onclick={onAdd}>Add new line</Button>
<Button icon={plus} style="margin-left: 8px" onclick={() => onAddWithInitValue({ name: 'Semi DSM', role: 'Designer' })}>Add new line with init value</Button>
{#each arrayFields as item (item.key)}
<div style="width: 1000px; display: flex">
<FormInput field={`${item.field}[name]`} label={`${item.field}.name`} style="width: 200px; margin-right: 16px" />
<FormSelect field={`${item.field}[role]`} label={`${item.field}.role`} optionList={roleOptions} style="width: 120px" />
<Button aria-label="Delete" icon={minus} style="margin: 12px" theme="borderless" type="danger" onclick={item.onRemove} />
</div>
{/each}
{/snippet}
</ArrayField>
<TextArea aria-label="Form state" style="margin-top: 10px" value={JSON.stringify(formState)} />
{/snippet}
</Form>Nesting ArrayField
ArrayField supports multi-level nesting. The following is an example of two-level nesting.
Code
<script lang="ts">
import { IconMinusCircle, IconPlusCircle } from '@fibrae/semi-icons-svelte';
import { ArrayField, Button, Card, Form, FormInput, FormSelect, TypographyText } from '@fibrae/semi-ui-svelte';
const selectOption = [
{ label: '发件人地址', value: 'address' },
{ label: '邮件主题', value: 'title' },
{ label: '发送时间', value: 'sendTime' },
{ label: '接收时间', value: 'receiveTime' },
{ label: '正文', value: 'main' },
{ label: '附件名称', value: 'attachmentName' },
];
const typeOption = [
{ label: '包含', value: 'include' },
{ label: '不包含', value: 'exclude' },
];
const initValue = {
group: [
{
name: '收信规则1',
rules: [
{ ruleType: 'address', type: 'include', text: 'bytedance.com' },
{ ruleType: 'title', type: 'exclude', text: '更新日志' },
],
},
{
name: '收信规则2',
rules: [{ ruleType: 'sendTime', type: 'include', text: '2019' }],
},
],
};
const newRule = {
name: '新规则名称',
rules: [
{ ruleType: 'main', type: 'include', text: '' },
{ ruleType: 'attachmentName', type: 'include', text: '' },
],
};
const rowStyle = 'margin-top: 12px; margin-left: 12px';
</script>
{#snippet plus()}
<IconPlusCircle />
{/snippet}
{#snippet minus()}
<IconMinusCircle />
{/snippet}
<Form allowEmpty initValues={initValue} labelPosition="left" onValueChange={(values) => console.log(values)} style="text-align: left">
<ArrayField field="group">
{#snippet children({ arrayFields, onAddWithInitValue })}
<Button icon={plus} theme="solid" onclick={() => onAddWithInitValue(newRule)}>新增收信规则</Button>
{#each arrayFields as item (item.key)}
<div style="width: 1000px; display: flex; flex-wrap: wrap">
<FormInput field={`${item.field}[name]`} label="规则名称" labelPosition="top" style="width: 600px" />
<Button aria-label="删除规则" icon={minus} style="margin: 36px 0 0 12px" type="danger" onclick={item.onRemove} />
<TypographyText strong style="flex-basis: 100%">当邮件到达,满足以下条件时:</TypographyText>
<Card shadows="hover" style="width: 620px; margin: 12px 0 0 24px">
<ArrayField field={`${item.field}.rules`}>
{#snippet children({ arrayFields: rules, onAddWithInitValue: addRule })}
{#each rules as rule, i (rule.key)}
<div style="display: flex">
<FormSelect field={`${rule.field}[ruleType]`} label={`${rule.field}.ruleType`} noLabel optionList={selectOption} style="width: 120px; margin-right: 12px" />
<FormSelect field={`${rule.field}[type]`} label={`${rule.field}.type`} noLabel optionList={typeOption} style="width: 100px; margin-right: 12px" />
<FormInput aria-label="条件内容" field={`${rule.field}[text]`} label={`${rule.field}.text`} noLabel style="width: 200px" />
<Button aria-label="删除条件" icon={minus} style={rowStyle} theme="borderless" type="danger" onclick={rule.onRemove} />
<Button aria-label="新增条件" disabled={i !== rules.length - 1} icon={plus} style={rowStyle} onclick={() => addRule({ ruleType: `条件${rules.length + 1}`, type: 'include' })} />
</div>
{/each}
{/snippet}
</ArrayField>
</Card>
</div>
{/each}
{/snippet}
</ArrayField>
</Form>Add or delete form items dynamically - by use formApi
If you don't use ArrayField, you can use the provided formApi to manually add or delete formState.
Code
<script lang="ts">
import { Button, Form, FormInput, FormSelect, TextArea, type FormApi } from '@fibrae/semi-ui-svelte';
interface Effect {
name: string;
type: string;
key: number;
}
interface FormValues {
effects: Effect[];
}
const initValues: FormValues = {
effects: [
{ name: '脸部贴纸', type: '2D', key: 1 },
{ name: '背景贴纸', type: '3D', key: 2 },
],
};
const typeOptions = [
{ label: '2D', value: '2D' },
{ label: '3D', value: '3D' },
];
let formApi: FormApi<FormValues> | undefined;
let nextKey = 3;
const add = () => {
const effects = formApi?.getValue('effects') ?? [];
formApi?.setValue('effects', [...effects, { name: '', type: '', key: nextKey++ }]);
};
const remove = (key: number) => {
const effects = (formApi?.getValue('effects') ?? []).filter((effect) => key !== effect.key);
formApi?.setValue('effects', effects.length ? effects : undefined);
};
</script>
<Form getFormApi={(api) => (formApi = api)} {initValues} labelPosition="left" labelWidth="180px" style="width: 500px">
{#snippet children({ values })}
<Button onclick={add}>添加</Button>
{#each values.effects ?? [] as effect, i (effect.key)}
<div style="width: 1000px; display: flex">
<FormInput aria-label={`特效 ${i + 1} 名称`} field={`effects[${i}].name`} style="width: 200px; margin-right: 12px" />
<FormSelect field={`effects[${i}].type`} optionList={typeOptions} style="width: 90px" />
<Button style="margin: 12px" type="danger" onclick={() => remove(effect.key)}>删除</Button>
</div>
{/each}
<TextArea aria-label="表单值" style="margin-top: 10px" value={JSON.stringify(values)} />
{/snippet}
</Form>Use of Hook
We provide four hooks so that you can easily access Form internal state and call Form and Field related api in a component placed inside the Form structure without passing through props. They depend on the context provided by Form, so call them while a component inside the Form initializes (the top level of <script>); calling them outside a Form throws an error
import { useFormApi, useFormState, useFieldApi, useFieldState } from '@fibrae/semi-ui-svelte';
useFormApi
useFormApi allows you to directly access the formApi of the parent Form component within a component inside the Form
Code
<script lang="ts">
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
import ChangeNameByFormApi from './parts/change-name-by-form-api.svelte';
</script>
<Form>
<FormInput field="name" initValue="mike" />
<ChangeNameByFormApi />
</Form>useFormState
useFormState allows you to directly access the form State of the parent Form component within a component inside the Form. It returns an accessor (() => FormState), read through formState(), which follows the form when read in the template
Code
<script lang="ts">
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
import ShowFormState from './parts/show-form-state.svelte';
</script>
<Form>
<FormInput field="name" initValue="mike" />
<h5>FormState read by 【useFormState】:</h5>
<ShowFormState />
</Form>useFieldApi
useFieldApi allows you to call the api of the specified Field directly within a component inside the Form. The parameter can be a field name or a function that returns one, which follows the field name when it changes
Code
<script lang="ts">
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
import ChangeNameByFieldApi from './parts/change-name-by-field-api.svelte';
</script>
<Form>
<FormInput field="name" initValue="mike" />
<ChangeNameByFieldApi />
</Form>useFieldState
useFieldState allows you to directly access the State of the specified Field within a component inside the Form. The parameter can be a field name or a function that returns one, and it returns an accessor: fieldState() gives { value, error, touched }
Code
<script lang="ts">
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
import ShowFieldState from './parts/show-field-state.svelte';
</script>
<Form layout="horizontal">
<div style="width: 400px">
<FormInput field="name" initValue="mike" />
<FormInput field="country" initValue="china" />
</div>
<div style="width: 500px; margin-top: 30px">
<ShowFieldState field="name" />
<ShowFieldState field="country" />
</div>
</Form>Access formApi and formState through the children snippet
Svelte has no withFormApi or withFormState. The children snippet of Form already provides formApi and formState; use useFormApi and useFormState above inside a child component
Access formApi
Code
<script lang="ts">
import { Button, Form, FormInput } from '@fibrae/semi-ui-svelte';
</script>
<Form>
{#snippet children({ formApi })}
<FormInput field="name" initValue="semi" />
<FormInput field="familyName" initValue="design" />
<Button htmlType="submit" style="margin-right: 4px">submit</Button>
<Button onclick={() => formApi.setValue('name', Math.random())}>Click Me!!! ChangeName By【formApi】</Button>
{/snippet}
</Form>Access formState
Code
<script lang="ts">
import { Form, FormInput } from '@fibrae/semi-ui-svelte';
</script>
<Form>
{#snippet children({ formState })}
<FormInput field="name" initValue="semi" />
<FormInput field="familyName" initValue="design" />
<code>{JSON.stringify(formState)}</code>
{/snippet}
</Form>Take over custom components
Svelte has no HOC. Use FormField to extend other custom components into Field, and Form will take over their behavior. It is the counterpart of withField in the other frameworks.
FormField did the following things.
- Take over the
valueof the component (or other properties specified byfieldOptions.valueKey), theonChangecallback (or other callbacks specified byonKeyChangeFnName) - Insert Field's
FormLabelabove the field - Insert Field's ErrorMessage under the field
- Insert Field's extraText under the field
For the specific configuration of fieldOptions, see fieldOptions
Your custom controlled component needs to do the following:
- Declare a bindable
value($bindable(), orcheckedorfileListspecified byvalueKey), follow its changes and update your component UI rendering results - When the value changes, call
onChange(or the callback specified byonKeyChangeFnName) with the latest value as the argument
<FormField component={YourComponent} field="name" {fieldOptions} />
The props type of FormField is inferred from the props of component: the component's own props plus Field Props, without value, checked, fileList, defaultValue and defaultChecked (the Form owns the value).
Code
<script lang="ts">
import { Form, FormField, Input } from '@fibrae/semi-ui-svelte';
</script>
<Form>
{#snippet children({ formState })}
<FormField component={Input} field="name" />
<pre><code>{JSON.stringify(formState)}</code></pre>
{/snippet}
</Form>Code
<script lang="ts">
import { Form, FormField } from '@fibrae/semi-ui-svelte';
import BaseInfo from './parts/base-info.svelte';
</script>
<Form>
{#snippet children({ formState })}
<FormField component={BaseInfo} field="baseInfo" label={{ text: '基本信息', required: true }} />
<pre><code>{JSON.stringify(formState)}</code></pre>
{/snippet}
</Form>API reference
Form
<script lang="ts">
import { Form } from '@fibrae/semi-ui-svelte';
</script>
Props
| Property | Description | Type | Default |
|---|---|---|---|
| allowEmpty | Whether a field whose value became empty keeps its key in values. The same prop on a field takes precedence. | boolean | false |
| autoScrollToError | Whether to scroll to the failing field when submit or formApi.validate fails; an object is passed on as options of scroll-into-view-if-needed. | boolean | ScrollOptions | false |
| children | Content of the form, with { formState, formApi, values }, for reading only; change the form through formApi. | TypeSnippet<[FormRenderProps<Values>]> | - |
| disabled | The disabled applied to every field; the disabled of a field takes precedence. | boolean | - |
| extraTextPosition | Position of extraText applied to every field: middle puts it between the Label and the control, bottom after the control. The same prop on a field takes precedence. | 'middle' | 'bottom' | - |
| form | The formApi returned by useForm, to control the form from outside the Form. | TypeFormApi<Values> | - |
| getFormApi | Called once the Form has mounted, with the formApi, to keep a reference to it. | Type(formApi: FormApi<Values>) => void | - |
| id | The id of the form element, also written to x-form-id; generated when omitted. | string | - |
| initValues | Initial values of the form, copied once when the Form is created. | Values | - |
| labelAlign | Label text alignment applied to every field. The same prop on a field takes precedence. | 'left' | 'right' | - |
| labelCol | Label grid layout applied to every field, with the span, offset and other props of Col; set it together with wrapperCol. | ColProps | - |
| labelPosition | Label position applied to every field; inset is only supported by some fields, the others lay out as top. The same prop on a field takes precedence. | 'top' | 'left' | 'inset' | "top" |
| labelWidth | Label width applied to every field; a number is in px. | number | string | - |
| layout | Direction the fields are laid out in, vertical or horizontal. | 'horizontal' | 'vertical' | "vertical" |
| onChange | Called when the form state updates, including field mount, unmount, value change, blur, and changes of validation status and errors, with the formState. | Type(formState: FormState<Values>) => void | - |
| onErrorChange | Called when the validation state updates, with all errors and the changed field errors. A batch such as validate, submit or reset calls it once, and silent validation does not call it. | Type(errors: AllErrors<Values>, changedError?: Partial<Record<FieldPath<Values>, FieldError>>) => void | - |
| onReset | Called when the reset button or formApi.reset resets the form. | Type() => void | - |
| onSubmit | Called after the submit button or formApi.submitForm and a passing validation, with the values and the submit event. | Type(values: Values, e?: SubmitEvent) => void | - |
| onSubmitFail | Called after the submit button or formApi.submitForm and a failing validation, with the errors, the values and the submit event. | Type(errors: FormValidateResult<Values>, values: Partial<Values>, e?: SubmitEvent) => void | - |
| onValueChange | Called when a field value changes, with all values and the changed field values. | Type(values: Values, changedValues: Partial<Values>) => void | - |
| showValidateIcon | Whether the icon of the status shows before the validation message. | boolean | true |
| stopPropagation | Whether the submit and reset events stop bubbling, to keep a nested Form from triggering the outer Form. | { submit?: boolean; reset?: boolean } | - |
| stopValidateWithError | Whether validation with rules stops at the first failing rule. The same prop on a field takes precedence. | boolean | - |
| trigger | When fields validate: change, blur, mount, custom or an array of them. The same prop on a field takes precedence. | FieldValidateTriggerType | - |
| validator | Form-level validation, called by submit and formApi.validate; it may return a Promise. Once set, the validator and rules of the fields no longer apply. An empty string or object means valid; on failure return an error object keyed by field name. | Type(values: Values) => FormValidateResult<Values> | Promise<FormValidateResult<Values>> | - |
| wrapperCol | Control grid layout applied to every field, with the span, offset and other props of Col; set it together with labelCol. | ColProps | - |
Methods
| Method | Description | Type |
|---|---|---|
| getFormApi | Returns the formApi; call it on the instance from bind:this. | Type() => FormApi<Values> |
class, style and native attributes and events (such as aria-* and onkeydown) land on the form element. When labelCol and wrapperCol are set, the form is wrapped in a Row, and the other attributes still land on the form. The form element has novalidate by default, so only Semi validates; pass novalidate={false} to restore native validation.
After getting the Form instance through bind:this, call its getFormApi() to get the formApi.
FormState
FormState stores all the state values within the Form, including the values of each field, error information, touched status
| Name | Instructions | Initial value | Example |
|---|---|---|---|
| values | Value Collection of the form | {} | {fieldA: 'str', fieldB: true} |
| errors | Form error information collection, you can decide whether to allow users to submit by judging whether there is error information | {} | {fieldA: 'length not valid'} |
| touched | The collection of fields the user has clicked on | {} | {fieldA: true} |
How to access the form state
- By calling
formApi.getFormState() - Via the children snippet, formState will injected as a parameter
- Via useFormState in a component inside the Form
- Via the formState created by useForm, read the reactive
formStateproperty outside the Form
FormApi
We provide FormApi. You have easy access to FormApi both inside and outside the Form, which allows you to use getter and setter to get and manipulate the values of FormState.
The table below describes the features available in the formApi.
| Function | Description | example |
|---|---|---|
| getFormProps | Get Form Component Props | formApi.getFormProps(propNames?: string[]) |
| getFormState | Get FormState | formApi.getFormState() |
| submitForm | Manually submit form operation | formApi.submitForm() |
| reset | Reset the form manually | formApi.reset(fields?: Array <string>) |
| validate | Manually trigger validation of the entire form. the verification of the entire Field will be triggered by default when no parameters are passed , if you want to trigger the verification of some fields, pass in the target field array After the Form level validator is configured, the Field level validator will not be triggered again when submit or formApi.validate() Silent validation: Pass { silent: true } to get validation results without triggering UI updates (no error messages shown, no touched state set). You can also use { fields: ['fieldA'], silent: true } to perform silent validation on specific fields. | formApi.validate() .then(values => {}) .catch(errors => {}) OR formApi.validate(['fieldA','fieldB']) OR formApi.validate({ silent: true }) OR formApi.validate({ fields: ['fieldA'], silent: true }) |
| setValues | Set the values of the entire form. The isOverride in the second parameter is false by default. When isOverride is false: Iterate through all registered fields in the Form, and update the values from newValues to formState.values. If a registered field is not included in newValues, that field will be set to undefined. When isOverride is true: Directly replace formState.values with newValues, including extra keys in newValues that don't have corresponding fields in the form. Recommended usage: If you only need to update some fields, it's recommended to use formApi.setValue(field, value) for individual updates, or get current values first then merge: formApi.setValues({ ...formApi.getValues(), name: newValue }) | formApi.setValues(newValues: object, {isOverride: boolean}) |
| getValues | Get the values of all Field | formApi.getValues() |
| setValue | provides direct modification of formState.values method. The difference from setValues is that it only modifies a single field. | formApi.setValue(field: string, newFieldValue: any) |
| getValue | Get the value of all / single Field | formApi.getValue() formApi.getValue(field: string) |
| setTouched | Modify formState.touched | formApi.setTouched(field: string, isTouched: boolean) |
| getTouched | Get the touched state of the Field | formApi.getTouched(field: string) |
| setError | Modify the error information of a field | formApi.setError(field: string, fieldErrorMessage: string) |
| getError | Get Error Status of Field | formApi.getError(field: string) |
| getFieldExist | Get whether the field exists in the Form | formApi.getFieldExist(field: string) |
| scrollToField | Scroll to the specified field, the second input parameter will be passed to scroll-into-view-if-needed | formApi.scrollToField(field: string, scrollOpts: ScrollIntoViewOptions) |
| scrollToError | Scroll to the field with validation error. You can pass a specified field or index. If you pass index, scroll to the index-th error DOM. If you do not pass any parameters, scroll to the first validation error position in the DOM tree. | formApi.scrollToError(ScrollToErrorOptions{field?: string; index?: number; scrollOpts?: ScrollIntoViewOptions }) |
How to access formApi
- Use
useForm()to create formApi outside Form, then pass it via theformproperty to the Form component. This allows you to get formApi before Form renders, suitable for scenarios where you need to control the form from outside the component. - After the Form component mounts, it calls the
getFormApicallback passed in props. You can save a reference to formApi in the callback function for subsequent calls (example code below) In addition, we provide other ways to get formApi, and you can choose different ways of calling - Use
bind:thisto get the Form instance and call itsgetFormApi() - Via the children snippet, formApi will injected as a parameter
- Via useFormApi for children component of Form
useForm
useForm() is used to create a formApi outside the Form component, suitable for scenarios where you need to control the form before it renders. It returns an object { formApi, formState, values }: formState and values are read-only properties that follow the form once bound through the form property of Form, when read in the template.
Usage example:
Code
<script lang="ts">
import { Button, Form, FormInput, useForm } from '@fibrae/semi-ui-svelte';
const form = useForm();
const handleClick = () => {
form.formApi.setValue('username', 'semi');
console.log('当前表单值:', form.values);
};
</script>
<Button onclick={handleClick}>外部设置值</Button>
<Button onclick={() => form.formApi.reset()}>重置表单</Button>
<Form form={form.formApi}>
<FormInput field="username" label="用户名" />
<FormInput field="email" label="邮箱" />
</Form>
<div style="margin-top: 16px">
<div>表单值:{JSON.stringify(form.values)}</div>
<div>touched 状态:{JSON.stringify(form.formState.touched)}</div>
</div><script lang="ts">
import { Button, Form, FormInput } from '@fibrae/semi-ui-svelte';
import type { FormApi } from '@fibrae/semi-ui-svelte';
let formApiA: FormApi | undefined;
let formB: { getFormApi: () => FormApi } | undefined;
const changeValues = () => {
// use formApi to update formA. After getting the formApi object, you can use it to make any changes you want to the form
formApiA?.setValues({ a: 1 });
// use formApi to update formB
formB?.getFormApi().setValues({ b: 2 });
};
</script>
<!-- save the formApi from the getFormApi callback -->
<Form getFormApi={(formApi) => (formApiA = formApi)}>
<FormInput field="a" />
</Form>
<!-- read the formApi from the Form instance received by bind:this -->
<Form bind:this={formB}>
<FormInput field="b" />
</Form>
<Button onclick={changeValues}>Change</Button>
Field Props
A Field is a component whose name starts with Form, such as FormInput and FormSelect, or one made by FormField; the tables below list its own properties.
Props
| Properties | Description | Type | Default |
|---|---|---|---|
| convert | After the field value changes, before it is stored in formState and shown, update the value of the field | (fieldValue) => any | |
| field | The mapping path of the field's value in formState.values. Form will use this value to distinguish the internal form control. Required!!! (except a FormCheckbox or FormRadio inside its group) | string | |
| label | The label text for this field. When not passed, it defaults to the same name as field. An object can set text, required, optional, align, width and disabled, and its text takes only a string or number. The fieldLabel and labelExtra snippets take precedence | string | number | FieldLabel | |
| labelPosition | Label position of this field. When passed on both Form and Field, the Field props prevail | 'top' | 'left' | 'inset' | |
| labelAlign | Text-align of the label text of this field. When passed on both Form and Field, the Field props prevail | 'left' | 'right' | |
| labelWidth | The width of the label text of this field, a number is in px. When passed on both Form and Field, the Field props prevail | string | number | |
| labelCol | Grid layout of the label of this field, with Col component. It overrides the same property of Form. When it is set together with wrapperCol while the Form does not set both, the Field wraps itself in a Row | ColProps | |
| wrapperCol | Grid layout of the control of this field, with Col component. It overrides the same property of Form. Used the same way as labelCol | ColProps | |
| noLabel | When you don't need to add label automatically, you can set this value to true | boolean | |
| noErrorMessage | When you don't need to add the ErrorMessage module automatically, you can set this value to true. Note that helpText is not shown either | boolean | |
| name | Field name. When passed in, the corresponding className will be automatically added to the field wrapper div, such as: money => '.semi-form-field-money'. It is also passed to the underlying component, for example to configure the name attribute of an input | string | |
| id | The id of the control, generated when omitted. The for of the label and the ids of the label, helpText, extraText and errormessage are derived from it | string | |
| fieldClassName | The className of the entire fieldWrapper is the same as the name parameter, except that the prefix is not automatically appended | string | |
| fieldStyle | The inline style of the entire fieldWrapper | string | |
| initValue | The initial value of the field (consumed only once when Field mounted, subsequent updates are invalid), it has higher priority than the values in Form's initValues | unknown (type depends on current component) | |
| validator | The custom validation function for this form control. Supports synchronous and asynchronous validation (by returning a promise). An empty string or undefined means valid; any other result is the error, and it can only be data: use the error snippet to render the error differently. When validator is set, rules will not take effect | (fieldValue, values) => string | string[] | boolean | undefined | Promise | |
| rules | validation rules, validation library based on async-validator | RuleItem[] | |
| validateStatus | The validation result status of this form control, which only affects the style, optional: success / error / warning / default. When omitted, the Field's own validation status is used | string | |
| trigger | The timing of triggering the verification, optional: blur / change / custom / mount, or an array of them 1. When set to custom, only formApi will trigger the verification 2.mount (triggered once when mounting) | FieldValidateTriggerType | 'change' |
| onChange | Callback when the value changes. The parameters are those of the callback of the wrapped component, followed by the latest form values latestValues, so you never read a stale value from a closure; a FormCheckbox or FormRadio without field is a plain control and has no latestValues (see the onChange of each component) | (...args, latestValues) => void | |
| transform | transform field values before validation. The transformed value is only used for validation and does not affect formState | (fieldValue) => any | |
| allowEmpty | Whether to keep the key of this field in values (as undefined) when its value is empty. Setting it on either the Field or the Form takes effect | boolean | false |
| allowEmptyString | Whether to allow values to be empty strings. When the value is '' by default, the key corresponding to this field will be removed from values. If you want to keep the key, you need to set allowEmptyString to true | boolean | false |
| emptyValue | The value that counts as empty. When the value changes to it, the key of this field is removed from values (kept as undefined when allowEmpty is true) | unknown | '' |
| keepState | Whether to retain the field state (value, error, touched) after the field is unmounted. When true, the field's value, validation message, and interaction state are preserved after unmounting, and will be automatically restored upon remounting | boolean | false |
| stopValidateWithError | When it is true, the rules check is used. After encountering the first rule that fails the check, it will no longer trigger the check of subsequent rules | boolean | false |
| pure | Whether to only take over the data stream, when true, it will not automatically insert modules such as ErrorMessage, Label, extraText, etc. The style and DOM structure are consistent with the original components | boolean | false |
| extraTextPosition | controls the display position of extraText. Middle (the vertical direction is displayed in the order of Label, extraText, and Field), bottom (the vertical direction is displayed in the order of Label, Field, and extraText); when passed on both Form and Field, the Field props prevail | 'middle' | 'bottom' | 'bottom' |
The other properties of the component (such as size and placeholder of Input) are written on the Field together with the properties above. The Field passes them to the wrapped component itself, not to the fieldWrapper. disabled takes the value of the Form and a disabled written on the Field wins.
Snippets
| Snippet | Description | Parameters |
|---|---|---|
| fieldLabel | Label content, takes precedence over the text in the label property | |
| labelExtra | Content that follows the label text | |
| helpText | Custom prompt information, which is displayed in the same block as the verification information. When both have values, the verification information is displayed first | |
| extraText | Additional prompt information, you can use this when both error information and prompt copy are required, after helpText/errorMessage | |
| error | Custom rendering of the error message | { error } |
The custom trigger snippet of FormSelect, FormCascader, FormTreeSelect, FormAutoComplete, FormDatePicker and FormTimePicker is named triggerContent on the Field and forwarded to the control's trigger, with the same parameters: the Field's own trigger prop is the validation timing. A Field with triggerContent and labelPosition inset is laid out as top.
Field Api
We also provide fieldApi, most of which is similar to formApi, with the difference that fieldApi limits the scope of modification, and it can only modify the bound field. Get it through useFieldApi
| Function | Instructions | example |
|---|---|---|
| setValue | Modify the value of the current Field | fieldApi.setValue(newValue: any) |
| getValue | Gets the value of the current Field | fieldApi.getValue() |
| setTouched | Modify the touched state of the current Field | fieldApi.setTouched(true) |
| getTouched | Get the touched state of the current Field | fieldApi.getTouched() |
| setError | Modify the error information of the current Field | fieldApi.setError(newErrorMessage: string) |
| getError | Gets field's error status | fieldApi.getError() |
ArrayField
For dynamically added and deleted array form items, we provide the ArrayField scope to simplify adding and removing rows
import { ArrayField } from '@fibrae/semi-ui-svelte';
Props
| Property | Description | Type | Default |
|---|---|---|---|
| children | Content of the rows, with { arrayFields, onAdd, onAddWithInitValue }. | TypeSnippet<[ArrayFieldSnippetProps]> | - |
| field | Path of the array in formState.values, required. For row fields such as a[0].name and a[1].name, pass a. | string | - |
| initValue | Initial value of the array, which must be an array; it takes precedence over the initValues of the Form. | unknown[] | - |
ArrayField has no DOM of its own, so undeclared attributes are dropped. The argument of the children snippet has this type:
interface ArrayFieldSnippetProps {
arrayFields: ArrayFieldItem[]; // The current array form, which can be used to render each row with {#each}
onAdd: (index?: number | Event) => void; // Add a blank line; a number inserts it at that position, an event is ignored so it can be used directly as onclick={onAdd}
onAddWithInitValue: (row: unknown, index?: number) => void; // Add a new row with an initial value, index is the insert position
}
interface ArrayFieldItem {
key: string; // A key used to identify the current row, to be used as the {#each} key
field: string; // This row fieldPath, which is equivalent to the field of ArrayField + [index]
onRemove: () => void; // Remove operation function of this line, when called, this line will be deleted directly
}
FormSection
import { FormSection } from '@fibrae/semi-ui-svelte';
Props
| Property | Description | Type | Default |
|---|---|---|---|
| children | The fields in the section. | Snippet | - |
| text | Title of the section; without content the title element is not rendered. | Snippet | - |
class, style and native attributes land on the section element.
FormLabel
By default, Label is self-inserted into each Field by Form.
If you need to self-insert Label elsewhere, we have provided the FormLabel component for you.
import { FormLabel } from '@fibrae/semi-ui-svelte';
Props
| Property | Description | Type | Default |
|---|---|---|---|
| align | Alignment of the Label text. | string | "left" |
| children | Label content, which takes precedence over text. | Snippet | - |
| disabled | Whether it has the disabled style. | boolean | - |
| extra | Content that follows the label text. | Snippet | - |
| name | The id of the labeled control, written to the for attribute of the label. | string | - |
| optional | Whether the optional text of the locale follows the text. The required * is then hidden. | boolean | false |
| required | Whether to show the required * mark. | boolean | false |
| text | Label text; the children snippet takes precedence. | string | number | - |
| width | Label width; a number is in px. | number | string | - |
class, style and native attributes land on the label element.
FormInputGroup
import { FormInputGroup } from '@fibrae/semi-ui-svelte';
Props
| Property | Description | Type | Default |
|---|---|---|---|
| aria-describedby | The aria-describedby of the Group, merged with the id of extraText on the group element. | string | null | - |
| aria-errormessage | Has no effect; the Group generates the id of the error message itself. | string | null | - |
| aria-labelledby | The aria-labelledby of the Group, merged with the id of the Label on the group element. | string | null | - |
| children | The fields in the group; only direct children with a field are rendered. | Snippet | - |
| disabled | The disabled of the fields in the group; the disabled of the Form applies when omitted. | boolean | - |
| extraText | Extra hint, always shown, referred to by the aria-describedby of every control in the group. | Snippet | - |
| extraTextPosition | Position of extraText: middle puts it between the Label and the Group, bottom after the Group. | 'bottom' | 'middle' | "bottom" |
| fieldLabel | Label content of the Group, which takes precedence over the text in the label prop. | Snippet | - |
| label | The Label of the whole Group: a string is the text, an object is a Label configuration whose id is the id of the Label; the fieldLabel snippet takes precedence. | string | number | (FieldLabel & { id?: string }) | - |
| labelExtra | Content that follows the Group Label text. | Snippet | - |
| labelPosition | Position of the Label, taken from the Form by default: top or left. | 'top' | 'left' | - |
| onBlur | Called when focus leaves the whole Group. | Type(event: FocusEvent) => void | - |
| onFocus | Called when focus enters the whole Group. | Type(event: FocusEvent) => void | - |
| size | Size of the controls in the group. | InputSize | - |
class, style and native attributes land on the InputGroup inside. aria-labelledby and aria-describedby are merged with the ids generated by the group, the caller's first. The root has role="group" when a label is set, and has none without a label.
When extraTextPosition is middle and labelPosition is left. Since the content of extraText has a variable height, the Label will no longer ensure that it can be aligned with the first line of text in the Field / FormInputGroup.
FormSlot
import { FormSlot } from '@fibrae/semi-ui-svelte';
Props
| Property | Description | Type | Default |
|---|---|---|---|
| children | The custom content to place. | Snippet | - |
| error | The error, rendered after the content; omitted when not given. | FieldError | - |
| errorContent | Custom rendering of the error, with { error }. | TypeSnippet<[{ error: FieldError }]> | - |
| fieldLabel | Label content, which takes precedence over the text in the label prop. | Snippet | - |
| label | The Label text, or a Label configuration such as text, align, width, required and optional; the fieldLabel snippet takes precedence. | string | number | FieldLabel | - |
| labelExtra | Content that follows the Label text. | Snippet | - |
| labelPosition | Position of the Label, taken from the Form by default: top or left. | 'top' | 'left' | - |
| noLabel | Whether to leave out the Label; with both labelCol and wrapperCol set on the Form, the Label column stays. | boolean | - |
class, style and native attributes land on the root element. The root element has role="group" when aria-label or aria-labelledby is passed, and a role passed by the caller wins. The label width and alignment come from labelWidth and labelAlign of the Form, and width and align in the label object win.
FormErrorMessage
import { FormErrorMessage } from '@fibrae/semi-ui-svelte';
- When the error is a string, render directly; when it is true, only the error container is rendered without text; when it is false, nothing is rendered
- When the error is an array, the errors in the array are aggregated and joined with commas; an error can only be data, use the
errorContentsnippet to render it differently
Props
| Property | Description | Type | Default |
|---|---|---|---|
| error | The error. A string is shown as it is, an array merges its errors; true only shows the error container without text; false and empty values render nothing. | FieldError | - |
| errorContent | Custom rendering of the error, with { error }. | TypeSnippet<[{ error: FieldError }]> | - |
| errorMessageId | The id of the error text element, for aria-errormessage to refer to. | string | - |
| helpText | Help text, shown when there is no error, in the same block as the error. | Snippet | - |
| helpTextId | The id of the help text element, for aria-describedby to refer to. | string | - |
| isInInputGroup | Whether it sits in an InputGroup; with true and showValidateIcon true, the error icon shows whatever validateStatus is. | boolean | - |
| showValidateIcon | Whether the icon of validateStatus shows. | boolean | - |
| validateStatus | Validation status of the message: default, error, warning or success. | ValidateStatus | - |
class, style and native attributes land on the root element.
fieldOptions
The fieldOptions property of FormField, which describes how it takes over the wrapped component.
| key | Description | Default |
|---|---|---|
| valueKey | The bindable property of the component that represents the value; only 'value', 'checked' and 'fileList' are supported, such as checked of Switch and value of Input | 'value' |
| onKeyChangeFnName | The name of the callback property the component calls when its value changes, generally onChange; the callback on the Field receives latestValues after the original arguments | 'onChange' |
| onBlurFnName | The name of the callback property the component calls when it loses focus; onblur for a component that forwards native events, an empty string for a component without one | 'onBlur' |
| valuePath | The path of the value in the first argument of the callback, such as target.checked of Checkbox and target.value of RadioGroup; not needed when the first argument is the value itself | '' |
| shouldInject | When false, a component without field is rendered as it is and not taken over by the Form, such as a FormCheckbox or FormRadio inside its group | true |
| insetLabel | When false, the component renders no inset label, and labelPosition inset is laid out as top | true |
| validateStatus | When false, the component has no validateStatus property and the Form no longer passes it, so it does not land on the DOM | true |
Accessibility
ARIA
- aria-labelledby、for
- Field component will automatically add label DOM. The id of the control is
props.id, generated when omitted; the for attribute of label is the same as this id; theidattribute of label is${control id}-label; - When the props.labelPosition of the Form or Field is set to
inset, there is no label tag at this time, but a div tag. The div tag corresponding to insetLabel will be automatically appended withid, the value is the same as the id of the above label, corresponding to thearia-labelledbyof the Field component. A field that does not support the inset label is laid out astop, and itsaria-labelledbypoints to the label actually rendered - The Field component will be automatically appended with
aria-labelledby, the value is the same as the id of the above label; thearia-labelledbypassed by the caller comes first and the two are merged instead of overwritten
- Field component will automatically add label DOM. The id of the control is
- aria-required
- When the Field is configured with required fields (that is, props.rules contains require: true or props.label is configured with required: true without optional: true), the Field component will be automatically appended with
aria-required = true(except CheckboxGroup, Slider, Upload and a single Radio); an aria-required passed on the Field takes precedence
- When the Field is configured with required fields (that is, props.rules contains require: true or props.label is configured with required: true without optional: true), the Field component will be automatically appended with
- aria-invalid 、aria-errormessage
- When the Field check fails, or validateStatus is error, the Field component will be automatically added with the
aria-invalid= true attribute. - When the Field check fails, the Field component will be automatically appended with the
aria-errormessageattribute, the value of which is the id of the DOM element corresponding to the errorMessage (format like:${control id}-errormessage); for CheckboxGroup it is set on every checkbox in the group.
- When the Field check fails, or validateStatus is error, the Field component will be automatically added with the
- aria-describedby
- When the Field is configured with
helpTextorextraText, the Field component will be automatically added with thearia-describedbyattribute, whose value is the id of the DOM element corresponding to helpText and extraText (format like:${control id}-helpText,${control id}-extraText); thearia-describedbypassed by the caller comes first and the two are merged instead of overwritten
- When the Field is configured with
Content Guidelines
- Form title
- The title of the form needs to follow the writing specification of the title
- Form label
- The label is a short description of the input box. The label is not a help text, so it should not be a description of the input
- Labels must:
- Place it above or below the input box
- Short (1-3 words)
- Use case conventions for statements (first letter uppercase, others lowercase)
- Help text
- Help text use statement writing conventions, capitalized
- Form button
- For the content Guidelines of the form button, refer to Button component
Design Tokens
| Sass variable | Value | Usage |
|---|---|---|
| $spacing-form_field_horizontal-paddingRight | $spacing-base | 水平布局表单项右侧内边距 |
| $spacing-form_field_group_horizontal-paddingRight | $spacing-base | 水平布局表单组标题右侧内边距 |
| $spacing-form_field_vertical-paddingTop | $spacing-base-tight | 表单项顶部内边距(垂直布局) |
| $spacing-form_field_vertical-paddingBottom | $spacing-base-tight | 表单项底部内边距(垂直布局) |
| $spacing-form_field_group_vertical-paddingTop | $spacing-base-tight | 垂直布局表单组顶部内边距 |
| $spacing-form_field_group_vertical-paddingBottom | $spacing-base-tight | 垂直布局表单组底部内边距 |
| $spacing-form_label-paddingRight | $spacing-base | 表单项标签右侧边距(水平布局) |
| $spacing-form_label-paddingTop | ($height-control-default - 20px) * 0.5 | 表单项标签顶部内边距(水平布局) |
| $spacing-form_label-marginBottom | $spacing-extra-tight | 表单项标签底部外边距 |
| $spacing-form_label-marginTop | 0px | 表单项标签顶部外边距 |
| $spacing-form_label_extra-marginLeft | $spacing-extra-tight | 表单项标签额外信息左侧边距 |
| $spacing-form_label_required-marginLeft | $spacing-extra-tight | 表单项标签必填标志左侧边距 |
| $spacing-form_label_posLeft-marginRight | 0 | 表单项左侧标签右侧外边距 |
| $spacing-form_label_posLeft-marginBottom | 0 | 表单项左侧标签底部外边距 |
| $spacing-form_label_posTop-paddingTop | $spacing-extra-tight | 表单项顶部标签顶部边距 |
| $spacing-form_label_posTop-paddingBottom | $spacing-extra-tight | 表单项顶部标签底部边距 |
| $spacing-form_extra_posMid-marginTop | $spacing-extra-tight | 表单项额外提示信息顶部外边距 - 额外信息处于中间时 |
| $spacing-form_extra_posMid-marginBottom | $spacing-extra-tight | 表单项额外提示信息底部外边距 - 额外信息处于中间时 |
| $spacing-form_extra_posBottom-marginTop | $spacing-extra-tight | 表单项额外提示信息顶部外边距 - 额外信息处于底部时 |
| $spacing-form_switch_rating_marginY | ($height-control-default - 24px) * 0.5 | Switch / Rating 表单项顶部内边距(水平布局) |
| $color-form_requiredMark_disabled-text-default | var(--semi-color-danger) | 禁用表单项必填标记颜色 |
| $color-form_label_disabled-text-default | var(--semi-color-disabled-text) | 禁用表单项标签文字颜色 |
| $font-form_label-fontWeight | $font-weight-bold | 表单项标签字重 |
| $color-form_label-text-default | var(--semi-color-text-0) | 表单项标签文字颜色 |
| $color-form_label_optional-text-default | var(--semi-color-tertiary) | 表单项标签可选标记颜色 |
| $color-form_label_extra-text-default | var(--semi-color-tertiary) | 表单项标签图标颜色 |
| $color-form_requiredMark-text-default | var(--semi-color-danger) | 必填标记颜色 |
| $font-form_requiredMark-fontWeight | $font-weight-bold | 表单必填标识字重 |
| $color-form_message_error-text-default | var(--semi-color-danger) | 错误提示颜色 |
| $color-form_alertIcon-icon-default | var(--semi-color-warning) | 警告图标颜色 |
| $spacing-form_statusIcon-marginRight | $spacing-extra-tight | 表单校验状态图标右侧外边距 |
| $spacing-form_message-marginTop | $spacing-extra-tight | 表单错误信息、辅助文字顶部外边距 |
| $color-form_section-text-default | var(--semi-color-text-0) | 表单分组标题文字颜色 |
| $color-form_section-border-default | var(--semi-color-border) | 表单分组标题底部描边颜色 |
| $width-form_section-border | $border-thickness-control | 表单分组标题底部描边宽度 |
| $spacing-form_section-marginTop | $spacing-super-loose - $spacing-base-tight | 表单分组顶部内边距 |
| $spacing-form_section_text-paddingBottom | $spacing-tight | 表单分组标题底部内边距 |
| $spacing-form_section_text-marginBottom | $spacing-extra-tight | 表单分组标题底部外边距 |
| $spacing-form_section_text-paddingTop | 0px | 表单分组标题顶部内边距 |
| $spacing-form_section_text-marginTop | 0px | 表单分组标题顶部外边距 |
FAQ
-
Why did I declare the form, modify the value, and the data is not automatically mapped to formState.values?
Check that the field has been passed correctly, and thefieldattribute on the Field component is a must-fill property ! -
Why doesn't the passed
defaultValueordefaultCheckedtake effect?
Refer to the beginning of the document Field. The Field component unifies the default value. You should pass the default value usinginitValueorinitValues -
Why did the component not change and the value not take effect after
initValueandinitValueswere updated asynchronously?
initValue,initValuesare only consumed once when Field and Form mount, and subsequent asynchronous updates will not take effect.
If your initial value needs to be taken remotely, you can update it usingformApi.setValue / setValuesafter you get the value
Or send a newkeydirectly to Form or Field to force it to remount. -
Why can't getValues get a certain field?
If the field has no initial value,
getValuescannot get this item. You can setinitValues/initValueor set theallowEmptyattribute to the form. -
Why does hitting enter on the input box trigger the form's submit?
This is standard HTML behavior. We do not plan to intervene and we will remain the same as the HTML. If there is really only one input element in the form, and you don't want to trigger the submit callback when you press Enter, it is recommended to use preventDefault for the enter of the keydown event of input to prevent the default behavior.
Click #767 for background and content.
-
The form will automatically save the historical input items, what should I do if I don't want this function?
Before v2.3, Form did not configurefor,name,idand other attributes for input controls strictly according to the A11y accessibility standard, so this function was not available in previous versions. After v2.3, we implemented it strictly according to the W3C standard. If you don't want the browser to automatically save history input items, you can also turn it off by settingautoComplete=offat the Form level or Field level