Field 组件为你的表单输入或所选组件提供了基本的布局、间距和功能。它们甚至可以扩展,以支持在两栏布局中布置复选框、单选按钮和开关。
<div class="form-field">
<label for="fieldText" class="form-label">Email address</label>
<input type="email" class="form-control" id="fieldText" placeholder="name@example.com">
<div class="form-text">
We’ll never share your email with anyone else.
</div>
</div>它是如何工作的
.form-field 包装器使用 CSS 网格来管理表单控件及其标签、描述和验证反馈的布局。
- 默认布局 是单列网格,所有内容垂直堆叠。用于文本输入、选择框、多行文本框、范围滑块、输入组和附加输入。
- 两列布局 当
:has()下的直接子元素为.check、.radio或.switch时会自动激活。控件固定在第一列,而标签、描述和反馈堆叠在第二列。 - 上方标签 — 当
.form-label与复选框、单选框或开关一起存在时,它跨越两列并位于控件行上方。 - 分组控件 — 将多个
.form-field嵌套在外部.form-field内,以在共享标签下对单选框、复选框或开关进行分组,并共享描述和验证反馈。
示例
选择
对<select>元素的作用方式相同。
<div class="form-field">
<label for="fieldSelect" class="form-label">Country</label>
<select class="form-control" id="fieldSelect">
<option selected>Choose one…</option>
<option value="1">United States</option>
<option value="2">Canada</option>
<option value="3">United Kingdom</option>
</select>
</div>文本区
对<textarea>元素也同样适用。描述是可选的。
<div class="form-field">
<label for="fieldTextarea" class="form-label">Message</label>
<textarea class="form-control" id="fieldTextarea" rows="3"></textarea>
<div class="form-text">
Be sure to include relevant details.
</div>
</div>复选框
当.check是直接子元素时,网格切换到两列布局,复选框在第1列,标签在第2列。
<div class="form-field">
<input type="checkbox" id="fieldCheck" class="check" />
<label for="fieldCheck">Remember me</label>
</div>收音机
单选按钮使用相同的两栏布局。将每个单选按钮 + 标签对包装在它自己的 .form-field 中。
<div class="form-field">
<input type="radio" id="fieldRadio1" class="radio" name="fieldRadioGroup" />
<label for="fieldRadio1">Option one</label>
</div>
<div class="form-field">
<input type="radio" id="fieldRadio2" class="radio" name="fieldRadioGroup" />
<label for="fieldRadio2">Option two</label>
</div>分组
将多个 .form-field 包裹在 .form-group 中,以在共享标签下分组相关控件。该组提供一致的间距,同时每个内部 .form-field 保持其自己的布局。
<div class="form-group">
<label class="form-label">Shipping method</label>
<div class="form-field">
<input type="radio" id="fieldGroupRadio1" class="radio" name="fieldGroupRadio" required />
<label for="fieldGroupRadio1">Standard</label>
</div>
<div class="form-field">
<input type="radio" id="fieldGroupRadio2" class="radio" name="fieldGroupRadio" required />
<label for="fieldGroupRadio2">Express</label>
</div>
<small class="form-text">Choose your preferred shipping speed.</small>
</div>组卡片
将 .form-field-card 添加到每个内部 .form-field,以获得突出显示所选选项的卡片式效果。将内部字段包裹在具有更紧密间距的 .form-group 中,以获得更紧凑的外观。
<div class="form-group">
<label class="form-label">Select a plan</label>
<div class="vstack gap-1">
<div class="form-field form-field-card">
<input type="radio" id="fieldRadioGroup1" class="radio" name="fieldRadioGrouped" checked />
<div class="form-field-content">
<label for="fieldRadioGroup1">Basic</label>
<small class="form-text">$9/month — For individuals.</small>
</div>
</div>
<div class="form-field form-field-card">
<input type="radio" id="fieldRadioGroup2" class="radio" name="fieldRadioGrouped" />
<div class="form-field-content">
<label for="fieldRadioGroup2">Pro</label>
<small class="form-text">$29/month — For teams.</small>
</div>
</div>
</div>
</div>开关
开关也会触发两栏网格。
<div class="form-field">
<div class="switch">
<input type="checkbox" id="fieldSwitch" role="switch" switch>
</div>
<label for="fieldSwitch">Enable notifications</label>
</div>组合框
组合框——以及日期选择器、一次性密码(OTP)和任何其他表单组件——也都受到支持。
<div class="form-field">
<label>Combobox</label>
<button class="form-control combobox-toggle" type="button"
data-bs-toggle="combobox"
data-bs-name="option"
data-bs-placeholder="Select an item…">
<span class="combobox-value">Select an item…</span>
<svg class="combobox-caret" width="10" height="16" viewBox="0 0 10 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.46967 5.46967C0.762563 5.17678 1.23744 5.17678 1.53033 5.46967L5 8.93934L8.46967 5.46967C8.76256 5.17678 9.23744 5.17678 9.53033 5.46967C9.82322 5.76256 9.82322 6.23744 9.53033 6.53033L5.53033 10.5303C5.23744 10.8232 4.76256 10.8232 4.46967 10.5303L0.46967 6.53033C0.176777 6.23744 0.176777 5.76256 0.46967 5.46967Z" fill="currentcolor"/></svg>
</button>
<div class="menu">
<button class="menu-item" type="button" data-bs-value="1">Option one</button>
<button class="menu-item" type="button" data-bs-value="2">Option two</button>
<button class="menu-item" type="button" data-bs-value="3">Option three</button>
</div>
<div class="form-text">Choose an item from the menu above.</div>
</div>开销标签
当复选框、单选按钮或开关需要在其上方显示字段标题时,添加一个 .form-label。它会自动跨越两列,并位于控件上方的独立行中。
<div class="form-field">
<label class="form-label">Terms and conditions</label>
<input type="checkbox" id="fieldOverheadCheck" class="check" required />
<div class="form-field-content">
<label for="fieldOverheadCheck">I agree to the terms</label>
<small class="form-text">Please read before agreeing.</small>
</div>
</div>描述
在标签后添加一个<small class="form-text">并将其包裹在.form-field-content中,以确保适当的间距和对齐。
<div class="form-field">
<input type="checkbox" id="fieldCheckDesc" class="check" />
<div class="form-field-content">
<label for="fieldCheckDesc">Subscribe to updates</label>
<small class="form-text">We’ll send you a weekly digest of what’s new.</small>
</div>
</div>
<div class="form-field">
<input type="radio" id="fieldRadioDesc" class="radio" name="fieldRadioDesc" />
<div class="form-field-content">
<label for="fieldRadioDesc">Standard shipping</label>
<small class="form-text">Arrives in 5-7 business days.</small>
</div>
</div>
<div class="form-field">
<label for="fieldTextDesc" class="form-label">Username</label>
<input type="text" class="form-control" id="fieldTextDesc">
<small class="form-text">Must be 3-20 characters, letters and numbers only.</small>
</div>验证
验证反馈 (.invalid-feedback, .valid-feedback) 在 .form-field 内对所有控件类型都能正确堆叠。
客户端
将 data-bs-validate 添加到你的 <form> 以选择 :user-invalid 样式。这些伪类在用户与控件交互后激活。要在有效字段上显示成功样式,请使用 data-bs-validate="valid"。添加 novalidate 可以在保留约束验证 API 的同时抑制浏览器的原生验证提示。
<form class="vstack gap-3" data-bs-validate novalidate>
<div class="form-field flex-1">
<label for="fieldValidateEmail" class="form-label">Email address</label>
<input type="email" class="form-control" id="fieldValidateEmail" required>
<div class="invalid-feedback">Please enter a valid email address.</div>
<div class="valid-feedback">Looks good!</div>
</div>
<div class="form-field">
<input type="checkbox" id="fieldValidateCheck" class="check" required>
<div class="form-field-content">
<label for="fieldValidateCheck">Agree to terms and conditions</label>
<div class="invalid-feedback">You must agree before submitting.</div>
</div>
</div>
<button class="btn-solid theme-primary align-self-start" type="submit">Submit</button>
</form>服务器端
将 .is-invalid 或 .is-valid 直接应用于控件以进行服务器端验证。页面加载时即可看到反馈。
<div class="form-field mb-3">
<label for="fieldServerEmail" class="form-label">Email address</label>
<input type="email" class="form-control is-invalid" id="fieldServerEmail" value="not-an-email" required>
<div class="invalid-feedback">Please enter a valid email address.</div>
</div>
<div class="form-field mb-3">
<label for="fieldServerName" class="form-label">Full name</label>
<input type="text" class="form-control is-valid" id="fieldServerName" value="Mark Otto" required>
<div class="valid-feedback">Looks good!</div>
</div>
<div class="form-field mb-3">
<input type="checkbox" id="fieldServerCheck" class="check is-invalid" required>
<label for="fieldServerCheck">Agree to terms and conditions</label>
<div class="invalid-feedback">You must agree before submitting.</div>
</div>
<div class="form-field mb-3">
<div class="switch">
<input type="checkbox" id="fieldServerSwitch" role="switch" switch class="is-invalid">
</div>
<label for="fieldServerSwitch">Accept marketing emails</label>
<div class="invalid-feedback">This field is required.</div>
</div>CSS
Sass
@layer forms {
.form-field {
position: relative;
display: grid;
gap: .5rem;
// width: 100%;
> label,
> .form-label {
justify-self: start;
margin-bottom: 0;
}
&:has(> .check, > .radio, > .switch) {
grid-template-columns: auto 1fr;
column-gap: .5rem;
align-items: start;
> .check,
> .radio,
> .switch {
grid-column: 1;
}
> :not(.check, .radio, .switch) {
grid-column: 2;
}
> .form-label {
grid-column: 1 / -1;
}
}
}
.form-field-content {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.form-field-card {
position: relative;
padding: calc(var(--spacer) * .75);
cursor: pointer;
border: var(--border-width) solid transparent;
@include border-radius(var(--radius-7));
&:hover {
background-color: var(--bg-1);
}
&:has(:checked) {
background-color: var(--bg-1);
border-color: var(--border-color);
}
label::before {
position: absolute;
inset: 0;
content: "";
}
}
.form-group {
display: grid;
gap: .5rem;
> label,
> .form-label,
> legend {
justify-self: start;
margin-bottom: 0;
}
}
}