Skip to content

fieldset 与 legend

<fieldset><legend> 是 HTML 中用于对表单控件进行逻辑分组的元素。<fieldset> 创建一个分组容器,<legend> 为该分组提供标题。它们不仅改善了表单的视觉组织,更重要的是提升了可访问性——屏幕阅读器可以宣读分组标题,帮助用户理解表单结构。

前置知识

阅读本节前,建议先了解:meter 度量元素

基础概念

什么是 fieldset 和 legend

  • <fieldset>:将相关的表单控件组织成一个逻辑组,浏览器默认渲染一个边框包围
  • <legend>:为 <fieldset> 提供标题,显示在边框上方

主要用途:

  • 视觉分组:用边框和标题将相关控件组织在一起
  • 可访问性:屏幕阅读器会朗读 legend,帮助用户理解表单结构
  • 批量操作:通过 disabled 属性一键禁用整个字段组
  • 语义化:表达"这些控件属于同一组"的关系

浏览器支持

<fieldset><legend> 是 HTML 最古老的元素之一,所有浏览器都完整支持。

语法

基本用法

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>fieldset 基本示例</title>
</head>
<body>
    <h2>用户注册</h2>
    <form action="/register" method="post">
        <fieldset>
            <legend>个人信息</legend>
            <div>
                <label for="name">姓名:</label>
                <input type="text" id="name" name="name">
            </div>
            <div>
                <label for="email">邮箱:</label>
                <input type="email" id="email" name="email">
            </div>
        </fieldset>

        <fieldset>
            <legend>安全设置</legend>
            <div>
                <label for="password">密码:</label>
                <input type="password" id="password" name="password">
            </div>
            <div>
                <label for="confirm">确认密码:</label>
                <input type="password" id="confirm" name="confirm">
            </div>
        </fieldset>

        <button type="submit">注册</button>
    </form>
</body>
</html>

详细说明

CSS 自定义样式

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>fieldset CSS</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }

        /* 默认样式去除 */
        fieldset {
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 16px 20px;
            margin-bottom: 16px;
        }

        /* legend 样式 */
        legend {
            font-weight: 600;
            font-size: 16px;
            padding: 0 8px;
            color: #333;
        }

        /* 卡片式风格 */
        .card-fieldset {
            border: 2px solid #4a90d9;
            background: #f8faff;
            border-radius: 12px;
        }
        .card-fieldset legend {
            color: #4a90d9;
            font-size: 18px;
        }

        /* 无边框风格 */
        .borderless-fieldset {
            border: none;
            padding: 0;
            margin-bottom: 24px;
        }
        .borderless-fieldset legend {
            font-size: 18px;
            border-bottom: 2px solid #eee;
            padding-bottom: 8px;
            width: 100%;
        }

        /* 禁用状态 */
        fieldset:disabled {
            opacity: 0.5;
            background: #f5f5f5;
        }
        .form-row {
            margin-bottom: 10px;
        }
        label {
            display: block;
            margin-bottom: 4px;
        }
        input {
            padding: 6px 10px;
            border: 2px solid #ddd;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <h2>fieldset 样式变体</h2>
    <form>
        <!-- 默认样式 -->
        <fieldset>
            <legend>联系方式</legend>
            <div class="form-row">
                <label for="phone">手机:</label>
                <input type="tel" id="phone" name="phone">
            </div>
            <div class="form-row">
                <label for="addr">地址:</label>
                <input type="text" id="addr" name="address">
            </div>
        </fieldset>

        <!-- 卡片样式 -->
        <fieldset class="card-fieldset">
            <legend>账户信息</legend>
            <div class="form-row">
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username">
            </div>
            <div class="form-row">
                <label for="pwd">密码:</label>
                <input type="password" id="pwd" name="password">
            </div>
        </fieldset>

        <!-- 无边框样式 -->
        <fieldset class="borderless-fieldset">
            <legend>偏好设置</legend>
            <div class="form-row">
                <label>
                    <input type="checkbox" name="newsletter" checked>
                    订阅新闻邮件
                </label>
            </div>
            <div class="form-row">
                <label>
                    <input type="checkbox" name="sms" checked>
                    接收短信通知
                </label>
            </div>
        </fieldset>
    </form>
</body>
</html>

disabled 禁用整组

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>fieldset disabled</title>
</head>
<body>
    <h2>地址信息</h2>
    <form>
        <fieldset>
            <legend>是否需要填写地址</legend>
            <label>
                <input type="checkbox" id="need-address" name="need_address" onchange="toggleAddress(this.checked)">
                我需要填写收货地址
            </label>
        </fieldset>

        <!-- 整组禁用:checked 后才启用 -->
        <fieldset id="address-fields" disabled>
            <legend>收货地址</legend>
            <div>
                <label for="province">省份:</label>
                <select id="province" name="province">
                    <option value="">请选择</option>
                    <option value="beijing">北京</option>
                    <option value="shanghai">上海</option>
                    <option value="guangdong">广东</option>
                </select>
            </div>
            <div>
                <label for="detail">详细地址:</label>
                <input type="text" id="detail" name="detail" disabled>
            </div>
        </fieldset>

        <button type="submit">提交</button>
    </form>

    <script>
        function toggleAddress(checked) {
            document.getElementById('address-fields').disabled = !checked;
            // fieldset disabled 会自动禁用内部所有控件
        }
    </script>
</body>
</html>

可访问性优势

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>fieldset 可访问性</title>
</head>
<body>
    <h2>fieldset 的可访问性</h2>
    <form>
        <!-- 屏幕阅读器会朗读:"个人信息 分组" -->
        <fieldset>
            <legend>个人信息</legend>
            <p><label for="first-name">名:<input type="text" id="first-name"></label></p>
            <p><label for="last-name">姓:<input type="text" id="last-name"></label></p>
        </fieldset>

        <!-- 屏幕阅读器会朗读:"联系方式 分组" -->
        <fieldset>
            <legend>联系方式</legend>
            <p><label for="tel">电话:<input type="tel" id="tel"></label></p>
            <p><label for="email">邮箱:<input type="email" id="email"></label></p>
        </fieldset>
    </form>

    <h3>ARIA 等价方式</h3>
    <p>
        如果无法使用 fieldset/legend,可以通过 ARIA role 实现类似效果:
    </p>
    <div role="group" aria-labelledby="group-title">
        <h3 id="group-title">付款信息</h3>
        <p><label for="card">卡号:<input type="text" id="card"></label></p>
    </div>
</body>
</html>

嵌套 fieldset

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>嵌套 fieldset</title>
</head>
<body>
    <h2>嵌套分组</h2>
    <form>
        <fieldset>
            <legend>收货信息</legend>
            <p><label for="name">收件人:<input type="text" id="name"></label></p>

            <!-- 嵌套分组 -->
            <fieldset>
                <legend>地址详情</legend>
                <p><label for="city">城市:<input type="text" id="city"></label></p>
                <p><label for="street">街道:<input type="text" id="street"></label></p>
            </fieldset>

            <p><label for="phone">电话:<input type="tel" id="phone"></label></p>
        </fieldset>
    </form>
</body>
</html>

实战示例

多步注册表单

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>多步注册表单</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
        fieldset {
            border: 2px solid #4a90d9;
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 16px;
            transition: opacity 0.3s;
        }
        fieldset:disabled {
            opacity: 0.4;
            border-color: #ccc;
        }
        legend {
            font-size: 18px;
            color: #4a90d9;
            font-weight: 600;
            padding: 0 12px;
        }
        .form-row { margin-bottom: 10px; }
        label { display: block; margin-bottom: 4px; font-weight: 500; }
        input {
            width: 100%;
            padding: 8px 12px;
            border: 2px solid #ddd;
            border-radius: 6px;
            box-sizing: border-box;
        }
        input:focus { border-color: #4a90d9; outline: none; }
        .actions { display: flex; gap: 8px; margin-top: 16px; }
        button {
            padding: 10px 24px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
        }
        .btn-primary { background: #4a90d9; color: #fff; }
        .btn-primary:disabled { background: #bbb; cursor: not-allowed; }
        .btn-secondary { background: #eee; }
        .btn-secondary:disabled { display: none; }
        .step-indicator {
            display: flex;
            gap: 4px;
            margin-bottom: 16px;
        }
        .step-dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #ddd;
            transition: background 0.3s;
        }
        .step-dot.active { background: #4a90d9; }
    </style>
</head>
<body>
    <h2>注册账号</h2>
    <form>
        <div class="step-indicator">
            <div class="step-dot active" id="dot-1"></div>
            <div class="step-dot" id="dot-2"></div>
            <div class="step-dot" id="dot-3"></div>
        </div>

        <fieldset id="step-1">
            <legend>第一步:基本信息</legend>
            <div class="form-row">
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-row">
                <label for="email">邮箱:</label>
                <input type="email" id="email" name="email" required>
            </div>
        </fieldset>

        <fieldset id="step-2" disabled>
            <legend>第二步:设置密码</legend>
            <div class="form-row">
                <label for="password">密码:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="form-row">
                <label for="confirm-pwd">确认密码:</label>
                <input type="password" id="confirm-pwd" name="confirm_pwd" required>
            </div>
        </fieldset>

        <fieldset id="step-3" disabled>
            <legend>第三步:个人资料</legend>
            <div class="form-row">
                <label for="nickname">昵称:</label>
                <input type="text" id="nickname" name="nickname">
            </div>
            <div class="form-row">
                <label for="birthday">生日:</label>
                <input type="date" id="birthday" name="birthday">
            </div>
        </fieldset>

        <div class="actions">
            <button type="button" id="prev-btn" class="btn-secondary" disabled>上一步</button>
            <button type="button" id="next-btn" class="btn-primary">下一步</button>
            <button type="submit" id="submit-btn" class="btn-primary" style="display: none;">完成注册</button>
        </div>
    </form>

    <script>
        let currentStep = 1;
        const totalSteps = 3;

        function showStep(step) {
            for (let i = 1; i <= totalSteps; i++) {
                const fs = document.getElementById(`step-${i}`);
                fs.disabled = i !== step;
                document.getElementById(`dot-${i}`).classList.toggle('active', i === step);
            }
            document.getElementById('prev-btn').disabled = step === 1;
            document.getElementById('next-btn').style.display = step === totalSteps ? 'none' : '';
            document.getElementById('submit-btn').style.display = step === totalSteps ? '' : 'none';
            currentStep = step;
        }

        document.getElementById('next-btn').addEventListener('click', () => {
            if (currentStep < totalSteps) showStep(currentStep + 1);
        });

        document.getElementById('prev-btn').addEventListener('click', () => {
            if (currentStep > 1) showStep(currentStep - 1);
        });
    </script>
</body>
</html>

注意事项

fieldset 和 div 的区别

使用 <div> 配合 CSS 边框也能实现类似视觉效果,但缺少语义化和可访问性支持:

html
<!-- 不推荐:无语义化 -->
<div style="border: 1px solid #ddd; padding: 12px;">
    <h3>个人信息</h3>
    <input type="text" name="name">
</div>

<!-- 推荐:语义化分组 -->
<fieldset>
    <legend>个人信息</legend>
    <input type="text" name="name">
</fieldset>

legend 的位置限制

<legend> 必须是 <fieldset> 的第一个子元素,否则不会正确显示。

disabled 会影响所有子控件

<fieldset> 设置 disabled 后,内部的所有表单控件(input、select、textarea、button)都会被禁用,值也不会提交。

最佳实践

  1. 按功能分组:将逻辑相关的表单控件放在同一个 fieldset 中
  2. 使用有意义的 legend:legend 应简洁明了地描述该组的功能
  3. 利用 disabled 控制条件字段:减少大量 JavaScript 操作
  4. CSS 统一样式:通过 CSS 让 fieldset/legend 与页面风格一致
  5. 多层嵌套要谨慎:避免超过 2 层嵌套,保持结构清晰

下一节

继续学习:name 与 value

参考链接