从基础到进阶 · 7 种下拉组件的设计、选择与交互实践

下拉组件是界面中最常见却又最容易被低估的交互元素。一次好的下拉体验,能让用户感到「刚刚好」。

在本文中,我们将深入探讨 7 种下拉组件 的设计意图、适用场景以及如何根据需求做出正确的选择。无论你是在搭建后台管理系统、电商平台,还是设计复杂的 B2B 工具,这份指南都能帮你找到最合适的下拉方案。

7 种下拉组件速览

  • 基础下拉 —— 最经典的单选菜单,适合简单选项。
  • 多选下拉 —— 允许用户同时选择多个选项。
  • 搜索下拉 —— 带过滤搜索功能,适合选项较多的场景。
  • 分组下拉 —— 将选项按类别分组,提升浏览效率。
  • 级联下拉 —— 联动选择,后一级选项依赖前一级。
  • 标签下拉 —— 以标签形式展示已选项,适合快速添加/移除。
  • 自定义下拉 —— 完全定制化,满足品牌或特殊交互需求。
核心原则: 下拉组件的选择应当基于 选项数量选择模式(单选/多选)、数据层级 以及 用户预期。没有「最好」的组件,只有「最合适」的组件。

如何选择使用?

面对不同的业务场景,我们可以通过以下维度来快速决策:

  • 选项数量: 少于 5 个 → 基础下拉 / 标签下拉;多于 10 个 → 搜索下拉或分组下拉。
  • 选择模式: 单选 → 基础下拉 / 搜索下拉;多选 → 多选下拉 / 标签下拉。
  • 数据依赖: 有级联关系 → 级联下拉;需要分类 → 分组下拉。
  • 品牌调性: 需要突出视觉风格 → 自定义下拉。

决策对照表

组件类型 推荐场景 选项数量 选择模式
基础下拉 简单配置、性别、状态 ≤ 8 单选
多选下拉 标签筛选、权限分配 ≤ 15 多选
搜索下拉 城市选择、用户搜索 ≥ 10 单选 / 多选
分组下拉 部门选择、分类目录 ≥ 10 单选
级联下拉 省市区、分类联动 动态 单选
标签下拉 技能选择、兴趣标签 ≤ 12 多选
自定义下拉 品牌定制、特殊交互 任意 任意

使用位置建议

下拉组件在页面中的位置直接影响用户的操作效率与认知负担。以下是一些通用建议:

  • 表单区域: 基础下拉、多选下拉、搜索下拉 是最常见的表单控件,通常与标签、输入框并列。
  • 筛选 / 搜索栏: 多选下拉、标签下拉 非常适合作为筛选条件,例如「按标签筛选」「按状态筛选」。
  • 导航 / 菜单: 自定义下拉 可以用于用户菜单、设置入口等,与品牌风格保持一致。
  • 数据录入: 级联下拉 在地址录入、分类选择等场景中表现优异。
位置技巧: 将下拉组件放在与上下文相关的区域,并确保下拉面板不会遮挡重要的操作按钮。对于长列表,建议将下拉面板向上展开(如果空间允许)。

下面,我们通过一个完整的交互预览,来感受这 7 种下拉组件的实际表现。每个组件都附有简短的说明,你可以直接点击体验。

基础下拉单选
Basic Dropdown
作用
最经典的单选菜单,适合选项数量较少的简单选择场景。
适合场景
性别、状态、简单配置项等固定短列表。
AGENT 提示词
生成一个单选基础下拉,选项包含「产品设计、前端开发、后端开发、数据分析」。
<div class="dd dd-basic" id="basic">
    <button type="button" class="dd-toggle">
        <span class="dd-label dd-placeholder">请选择</span>
        <i class="fas fa-chevron-down dd-arrow"></i>
    </button>
    <ul class="dd-menu">
        <li data-value="design">产品设计</li>
        <li data-value="frontend">前端开发</li>
        <li data-value="backend">后端开发</li>
        <li data-value="data">数据分析</li>
    </ul>
</div>
.dd{position:relative;width:100%;min-width:190px;max-width:340px}
.dd .dd-toggle{
width:100%;padding:10px 16px;background:var(--ti-field);border:1.5px solid var(--ti-border);
border-radius:12px;font-size:.95rem;color:var(--ti-fg);cursor:pointer;display:flex;
justify-content:space-between;align-items:center;gap:8px;text-align:left;
transition:border-color .2s,background .2s;
}
.dd .dd-toggle:hover{border-color:var(--ti-accent);background:var(--ti-field-hover)}
.dd .dd-toggle .dd-arrow{transition:transform .2s;flex:none;font-size:.8rem;color:var(--ti-muted)}
.dd.is-open .dd-toggle .dd-arrow{transform:rotate(180deg)}
.dd .dd-menu{
position:absolute;top:calc(100% + 6px);left:0;width:100%;background:var(--ti-panel);
border-radius:14px;box-shadow:var(--ti-shadow);border:1px solid var(--ti-line);
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s,transform .2s,visibility .2s;z-index:100;
list-style:none;margin:0;padding:6px 0;overflow:hidden;
}
.dd.is-open .dd-menu{opacity:1;visibility:visible;transform:translateY(0)}
.dd .dd-menu li{
padding:10px 18px;cursor:pointer;transition:background .15s;font-size:.95rem;
color:var(--ti-fg);list-style:none;display:flex;align-items:center;gap:10px;
}
.dd .dd-menu li:hover{background:var(--ti-accent-ghost)}
.dd .dd-menu li.is-selected{background:var(--ti-accent-soft);font-weight:500;color:var(--ti-accent-deep)}
.dd .dd-placeholder{color:var(--ti-muted)}
.dd .dd-check{width:18px;text-align:center;color:var(--ti-accent);flex:none}
.dd .dd-check.is-hidden{opacity:0}
function ddInit(root){
    var toggle = root.querySelector('.dd-toggle');
    var menu = root.querySelector('.dd-menu');
    if (!toggle || !menu) { return null; }

    function close(){ root.classList.remove('is-open'); }
    function open(){ root.classList.add('is-open'); }
    function isOpen(){ return root.classList.contains('is-open'); }

    toggle.addEventListener('click', function(e){
        e.stopPropagation();
        if (isOpen()) { close(); } else { open(); }
    });
    document.addEventListener('click', function(e){
        if (!root.contains(e.target)) { close(); }
    });
    document.addEventListener('keydown', function(e){
        if (e.key === 'Escape') { close(); }
    });

    return { root: root, toggle: toggle, menu: menu, open: open, close: close, isOpen: isOpen };
}
var dd = ddInit(document.getElementById('basic'));
if (dd) {
    var label = dd.root.querySelector('.dd-label');
    dd.menu.querySelectorAll('li').forEach(function(li){
        li.addEventListener('click', function(e){
            e.stopPropagation();
            label.textContent = li.textContent.trim();
            label.classList.remove('dd-placeholder');
            dd.menu.querySelectorAll('li').forEach(function(x){ x.classList.remove('is-selected'); });
            li.classList.add('is-selected');
            dd.close();
        });
    });
}
多选下拉多选
Multi-select Dropdown
作用
允许同时选择多个选项,已选项以标签形式展示,便于快速管理。
适合场景
标签筛选、权限分配、兴趣多选等场景。
AGENT 提示词
生成一个多选下拉,已选项以标签展示并可删除,选项含 Vue.js、React、Angular、Svelte。
<div class="dd dd-multi" id="multi">
    <div class="dd-toggle" role="button" tabindex="0">
        <span class="dd-tags"><span class="dd-placeholder">请选择</span></span>
        <i class="fas fa-chevron-down dd-arrow"></i>
    </div>
    <ul class="dd-menu">
        <li data-value="vue"><span class="dd-check is-hidden">✓</span> Vue.js</li>
        <li data-value="react"><span class="dd-check is-hidden">✓</span> React</li>
        <li data-value="angular"><span class="dd-check is-hidden">✓</span> Angular</li>
        <li data-value="svelte"><span class="dd-check is-hidden">✓</span> Svelte</li>
    </ul>
</div>
.dd{position:relative;width:100%;min-width:190px;max-width:340px}
.dd .dd-toggle{
width:100%;padding:10px 16px;background:var(--ti-field);border:1.5px solid var(--ti-border);
border-radius:12px;font-size:.95rem;color:var(--ti-fg);cursor:pointer;display:flex;
justify-content:space-between;align-items:center;gap:8px;text-align:left;
transition:border-color .2s,background .2s;
}
.dd .dd-toggle:hover{border-color:var(--ti-accent);background:var(--ti-field-hover)}
.dd .dd-toggle .dd-arrow{transition:transform .2s;flex:none;font-size:.8rem;color:var(--ti-muted)}
.dd.is-open .dd-toggle .dd-arrow{transform:rotate(180deg)}
.dd .dd-menu{
position:absolute;top:calc(100% + 6px);left:0;width:100%;background:var(--ti-panel);
border-radius:14px;box-shadow:var(--ti-shadow);border:1px solid var(--ti-line);
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s,transform .2s,visibility .2s;z-index:100;
list-style:none;margin:0;padding:6px 0;overflow:hidden;
}
.dd.is-open .dd-menu{opacity:1;visibility:visible;transform:translateY(0)}
.dd .dd-menu li{
padding:10px 18px;cursor:pointer;transition:background .15s;font-size:.95rem;
color:var(--ti-fg);list-style:none;display:flex;align-items:center;gap:10px;
}
.dd .dd-menu li:hover{background:var(--ti-accent-ghost)}
.dd .dd-menu li.is-selected{background:var(--ti-accent-soft);font-weight:500;color:var(--ti-accent-deep)}
.dd .dd-placeholder{color:var(--ti-muted)}
.dd .dd-check{width:18px;text-align:center;color:var(--ti-accent);flex:none}
.dd .dd-check.is-hidden{opacity:0}
.dd-multi .dd-toggle{min-height:46px;flex-wrap:wrap}
.dd-multi .dd-tags{display:flex;flex-wrap:wrap;gap:4px;flex:1}
.dd-multi .dd-tag{
background:var(--ti-accent-soft);padding:2px 8px 2px 12px;border-radius:30px;
font-size:.8rem;color:var(--ti-accent-deep);display:inline-flex;align-items:center;
gap:6px;font-weight:500;
}
.dd-multi .dd-tag i{cursor:pointer;font-size:.7rem;opacity:.7;transition:opacity .2s}
.dd-multi .dd-tag i:hover{opacity:1}
.dd-multi .dd-menu{max-height:200px;overflow-y:auto}
function ddInit(root){
    var toggle = root.querySelector('.dd-toggle');
    var menu = root.querySelector('.dd-menu');
    if (!toggle || !menu) { return null; }

    function close(){ root.classList.remove('is-open'); }
    function open(){ root.classList.add('is-open'); }
    function isOpen(){ return root.classList.contains('is-open'); }

    toggle.addEventListener('click', function(e){
        e.stopPropagation();
        if (isOpen()) { close(); } else { open(); }
    });
    document.addEventListener('click', function(e){
        if (!root.contains(e.target)) { close(); }
    });
    document.addEventListener('keydown', function(e){
        if (e.key === 'Escape') { close(); }
    });

    return { root: root, toggle: toggle, menu: menu, open: open, close: close, isOpen: isOpen };
}
var dd = ddInit(document.getElementById('multi'));
if (dd) {
    var box = dd.root.querySelector('.dd-tags');
    var picked = [];

    function labelOf(val){
        var li = dd.menu.querySelector('li[data-value="' + val + '"]');
        return li ? li.textContent.trim() : val;
    }
    function render(){
        if (!picked.length) {
            box.innerHTML = '<span class="dd-placeholder">请选择</span>';
            return;
        }
        box.innerHTML = picked.map(function(v){
            return '<span class="dd-tag">' + labelOf(v) +
                   ' <i class="fas fa-times" data-value="' + v + '"></i></span>';
        }).join('');
        box.querySelectorAll('.dd-tag i').forEach(function(icon){
            icon.addEventListener('click', function(e){
                e.stopPropagation();
                toggleVal(icon.getAttribute('data-value'));
            });
        });
    }
    function toggleVal(val){
        var i = picked.indexOf(val);
        var li = dd.menu.querySelector('li[data-value="' + val + '"]');
        var check = li ? li.querySelector('.dd-check') : null;
        if (i > -1) {
            picked.splice(i, 1);
            if (check) { check.classList.add('is-hidden'); }
        } else {
            picked.push(val);
            if (check) { check.classList.remove('is-hidden'); }
        }
        render();
    }

    dd.menu.querySelectorAll('li').forEach(function(li){
        li.addEventListener('click', function(e){
            e.stopPropagation();
            toggleVal(li.getAttribute('data-value'));
        });
    });
}
搜索下拉带过滤
Searchable Dropdown
作用
内置关键词过滤,适合选项较多的数据检索场景。
适合场景
城市选择、用户搜索、长列表检索等。
AGENT 提示词
生成一个带搜索过滤的下拉,输入关键词实时筛选城市列表。
<div class="dd dd-search" id="search">
    <button type="button" class="dd-toggle">
        <span class="dd-label dd-placeholder">选择城市</span>
        <i class="fas fa-chevron-down dd-arrow"></i>
    </button>
    <div class="dd-menu">
        <input type="text" class="dd-input" placeholder="搜索城市…" />
        <ul class="dd-options">
            <li data-value="beijing">北京</li>
            <li data-value="shanghai">上海</li>
            <li data-value="guangzhou">广州</li>
            <li data-value="shenzhen">深圳</li>
            <li data-value="hangzhou">杭州</li>
            <li data-value="chengdu">成都</li>
            <li data-value="wuhan">武汉</li>
        </ul>
        <div class="dd-empty" hidden>未找到匹配项</div>
    </div>
</div>
.dd{position:relative;width:100%;min-width:190px;max-width:340px}
.dd .dd-toggle{
width:100%;padding:10px 16px;background:var(--ti-field);border:1.5px solid var(--ti-border);
border-radius:12px;font-size:.95rem;color:var(--ti-fg);cursor:pointer;display:flex;
justify-content:space-between;align-items:center;gap:8px;text-align:left;
transition:border-color .2s,background .2s;
}
.dd .dd-toggle:hover{border-color:var(--ti-accent);background:var(--ti-field-hover)}
.dd .dd-toggle .dd-arrow{transition:transform .2s;flex:none;font-size:.8rem;color:var(--ti-muted)}
.dd.is-open .dd-toggle .dd-arrow{transform:rotate(180deg)}
.dd .dd-menu{
position:absolute;top:calc(100% + 6px);left:0;width:100%;background:var(--ti-panel);
border-radius:14px;box-shadow:var(--ti-shadow);border:1px solid var(--ti-line);
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s,transform .2s,visibility .2s;z-index:100;
list-style:none;margin:0;padding:6px 0;overflow:hidden;
}
.dd.is-open .dd-menu{opacity:1;visibility:visible;transform:translateY(0)}
.dd .dd-menu li{
padding:10px 18px;cursor:pointer;transition:background .15s;font-size:.95rem;
color:var(--ti-fg);list-style:none;display:flex;align-items:center;gap:10px;
}
.dd .dd-menu li:hover{background:var(--ti-accent-ghost)}
.dd .dd-menu li.is-selected{background:var(--ti-accent-soft);font-weight:500;color:var(--ti-accent-deep)}
.dd .dd-placeholder{color:var(--ti-muted)}
.dd .dd-check{width:18px;text-align:center;color:var(--ti-accent);flex:none}
.dd .dd-check.is-hidden{opacity:0}
.dd-search .dd-menu{padding:0}
.dd-search .dd-input{
width:100%;padding:11px 16px;border:none;border-bottom:1px solid var(--ti-line);
font-size:.95rem;outline:none;background:var(--ti-field);color:var(--ti-fg);
}
.dd-search .dd-input:focus{background:var(--ti-field-hover)}
.dd-search .dd-options{max-height:168px;overflow-y:auto;list-style:none;margin:0;padding:4px 0}
.dd-search .dd-options li.is-hidden{display:none}
.dd-search .dd-empty{padding:14px 18px;color:var(--ti-muted);font-size:.9rem;text-align:center}
function ddInit(root){
    var toggle = root.querySelector('.dd-toggle');
    var menu = root.querySelector('.dd-menu');
    if (!toggle || !menu) { return null; }

    function close(){ root.classList.remove('is-open'); }
    function open(){ root.classList.add('is-open'); }
    function isOpen(){ return root.classList.contains('is-open'); }

    toggle.addEventListener('click', function(e){
        e.stopPropagation();
        if (isOpen()) { close(); } else { open(); }
    });
    document.addEventListener('click', function(e){
        if (!root.contains(e.target)) { close(); }
    });
    document.addEventListener('keydown', function(e){
        if (e.key === 'Escape') { close(); }
    });

    return { root: root, toggle: toggle, menu: menu, open: open, close: close, isOpen: isOpen };
}
var dd = ddInit(document.getElementById('search'));
if (dd) {
    var label = dd.root.querySelector('.dd-label');
    var input = dd.root.querySelector('.dd-input');
    var empty = dd.root.querySelector('.dd-empty');
    var items = dd.root.querySelectorAll('.dd-options li');

    dd.toggle.addEventListener('click', function(){
        if (dd.isOpen()) { setTimeout(function(){ input.focus(); }, 80); }
    });

    input.addEventListener('click', function(e){ e.stopPropagation(); });
    input.addEventListener('input', function(){
        var q = input.value.toLowerCase().trim();
        var found = 0;
        items.forEach(function(li){
            var hit = li.textContent.toLowerCase().indexOf(q) > -1;
            li.classList.toggle('is-hidden', !hit);
            if (hit) { found++; }
        });
        empty.hidden = found > 0;
    });

    items.forEach(function(li){
        li.addEventListener('click', function(e){
            e.stopPropagation();
            label.textContent = li.textContent.trim();
            label.classList.remove('dd-placeholder');
            dd.close();
            input.value = '';
            items.forEach(function(x){ x.classList.remove('is-hidden'); });
            empty.hidden = true;
        });
    });
}
分组下拉分类
Grouped Dropdown
作用
将选项按类别分组,提升长列表的浏览与定位效率。
适合场景
部门选择、分类目录、机构树等层级数据。
AGENT 提示词
生成一个分组下拉,按「技术线 / 产品线 / 运营线」分组展示岗位选项。
<div class="dd dd-grouped" id="grouped">
    <button type="button" class="dd-toggle">
        <span class="dd-label dd-placeholder">选择部门</span>
        <i class="fas fa-chevron-down dd-arrow"></i>
    </button>
    <ul class="dd-menu">
        <li class="dd-group">技术线</li>
        <li data-value="frontend">前端开发</li>
        <li data-value="backend">后端开发</li>
        <li data-value="devops">运维开发</li>
        <li class="dd-group">产品线</li>
        <li data-value="pm">产品经理</li>
        <li data-value="design">UI/UX 设计</li>
        <li class="dd-group">运营线</li>
        <li data-value="content">内容运营</li>
        <li data-value="growth">增长运营</li>
    </ul>
</div>
.dd{position:relative;width:100%;min-width:190px;max-width:340px}
.dd .dd-toggle{
width:100%;padding:10px 16px;background:var(--ti-field);border:1.5px solid var(--ti-border);
border-radius:12px;font-size:.95rem;color:var(--ti-fg);cursor:pointer;display:flex;
justify-content:space-between;align-items:center;gap:8px;text-align:left;
transition:border-color .2s,background .2s;
}
.dd .dd-toggle:hover{border-color:var(--ti-accent);background:var(--ti-field-hover)}
.dd .dd-toggle .dd-arrow{transition:transform .2s;flex:none;font-size:.8rem;color:var(--ti-muted)}
.dd.is-open .dd-toggle .dd-arrow{transform:rotate(180deg)}
.dd .dd-menu{
position:absolute;top:calc(100% + 6px);left:0;width:100%;background:var(--ti-panel);
border-radius:14px;box-shadow:var(--ti-shadow);border:1px solid var(--ti-line);
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s,transform .2s,visibility .2s;z-index:100;
list-style:none;margin:0;padding:6px 0;overflow:hidden;
}
.dd.is-open .dd-menu{opacity:1;visibility:visible;transform:translateY(0)}
.dd .dd-menu li{
padding:10px 18px;cursor:pointer;transition:background .15s;font-size:.95rem;
color:var(--ti-fg);list-style:none;display:flex;align-items:center;gap:10px;
}
.dd .dd-menu li:hover{background:var(--ti-accent-ghost)}
.dd .dd-menu li.is-selected{background:var(--ti-accent-soft);font-weight:500;color:var(--ti-accent-deep)}
.dd .dd-placeholder{color:var(--ti-muted)}
.dd .dd-check{width:18px;text-align:center;color:var(--ti-accent);flex:none}
.dd .dd-check.is-hidden{opacity:0}
.dd-grouped .dd-menu{max-height:230px;overflow-y:auto}
.dd-grouped .dd-menu li{padding-left:28px}
.dd-grouped .dd-menu li.dd-group{
padding:8px 18px 4px;font-size:.7rem;text-transform:uppercase;letter-spacing:.5px;
color:var(--ti-muted);font-weight:600;cursor:default;
}
.dd-grouped .dd-menu li.dd-group:hover{background:transparent}
function ddInit(root){
    var toggle = root.querySelector('.dd-toggle');
    var menu = root.querySelector('.dd-menu');
    if (!toggle || !menu) { return null; }

    function close(){ root.classList.remove('is-open'); }
    function open(){ root.classList.add('is-open'); }
    function isOpen(){ return root.classList.contains('is-open'); }

    toggle.addEventListener('click', function(e){
        e.stopPropagation();
        if (isOpen()) { close(); } else { open(); }
    });
    document.addEventListener('click', function(e){
        if (!root.contains(e.target)) { close(); }
    });
    document.addEventListener('keydown', function(e){
        if (e.key === 'Escape') { close(); }
    });

    return { root: root, toggle: toggle, menu: menu, open: open, close: close, isOpen: isOpen };
}
var dd = ddInit(document.getElementById('grouped'));
if (dd) {
    var label = dd.root.querySelector('.dd-label');
    dd.menu.querySelectorAll('li:not(.dd-group)').forEach(function(li){
        li.addEventListener('click', function(e){
            e.stopPropagation();
            label.textContent = li.textContent.trim();
            label.classList.remove('dd-placeholder');
            dd.menu.querySelectorAll('li').forEach(function(x){ x.classList.remove('is-selected'); });
            li.classList.add('is-selected');
            dd.close();
        });
    });
}
级联下拉联动
Cascading Dropdown
作用
选项之间存在层级依赖,后一级随前一级选择联动更新。
适合场景
省市区选择、分类联动、地址录入等。
AGENT 提示词
生成一个两级级联下拉,选择省份后联动刷新对应城市列表。
<div class="dd dd-cascade" id="cascade">
    <div class="dd-row">
        <select class="dd-select" id="province">
            <option value="">省份</option>
            <option value="gd">广东</option>
            <option value="zj">浙江</option>
            <option value="sc">四川</option>
        </select>
        <select class="dd-select" id="city">
            <option value="">城市</option>
        </select>
    </div>
    <div class="dd-result" id="result">请先选择省份</div>
</div>
.dd{position:relative;width:100%;min-width:190px;max-width:340px}
.dd .dd-toggle{
width:100%;padding:10px 16px;background:var(--ti-field);border:1.5px solid var(--ti-border);
border-radius:12px;font-size:.95rem;color:var(--ti-fg);cursor:pointer;display:flex;
justify-content:space-between;align-items:center;gap:8px;text-align:left;
transition:border-color .2s,background .2s;
}
.dd .dd-toggle:hover{border-color:var(--ti-accent);background:var(--ti-field-hover)}
.dd .dd-toggle .dd-arrow{transition:transform .2s;flex:none;font-size:.8rem;color:var(--ti-muted)}
.dd.is-open .dd-toggle .dd-arrow{transform:rotate(180deg)}
.dd .dd-menu{
position:absolute;top:calc(100% + 6px);left:0;width:100%;background:var(--ti-panel);
border-radius:14px;box-shadow:var(--ti-shadow);border:1px solid var(--ti-line);
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s,transform .2s,visibility .2s;z-index:100;
list-style:none;margin:0;padding:6px 0;overflow:hidden;
}
.dd.is-open .dd-menu{opacity:1;visibility:visible;transform:translateY(0)}
.dd .dd-menu li{
padding:10px 18px;cursor:pointer;transition:background .15s;font-size:.95rem;
color:var(--ti-fg);list-style:none;display:flex;align-items:center;gap:10px;
}
.dd .dd-menu li:hover{background:var(--ti-accent-ghost)}
.dd .dd-menu li.is-selected{background:var(--ti-accent-soft);font-weight:500;color:var(--ti-accent-deep)}
.dd .dd-placeholder{color:var(--ti-muted)}
.dd .dd-check{width:18px;text-align:center;color:var(--ti-accent);flex:none}
.dd .dd-check.is-hidden{opacity:0}
.dd-cascade{display:flex;flex-direction:column;gap:12px;max-width:360px}
.dd-cascade .dd-row{display:flex;gap:10px;flex-wrap:wrap}
.dd-cascade .dd-select{
flex:1;min-width:110px;padding:10px 12px;background:var(--ti-field);
border:1.5px solid var(--ti-border);border-radius:12px;font-size:.9rem;
color:var(--ti-fg);outline:none;cursor:pointer;transition:border-color .2s;
}
.dd-cascade .dd-select:hover{border-color:var(--ti-accent)}
.dd-cascade .dd-select:focus{border-color:var(--ti-accent-deep)}
.dd-cascade .dd-result{font-size:.85rem;color:var(--ti-muted)}
var province = document.getElementById('province');
var city = document.getElementById('city');
var result = document.getElementById('result');
var MAP = {
    gd: { name: '广东', cities: ['广州', '深圳', '珠海'] },
    zj: { name: '浙江', cities: ['杭州', '宁波', '温州'] },
    sc: { name: '四川', cities: ['成都', '绵阳', '乐山'] }
};

province.addEventListener('change', function(){
    var val = province.value;
    city.innerHTML = '<option value="">城市</option>';
    if (val && MAP[val]) {
        MAP[val].cities.forEach(function(c){
            var opt = document.createElement('option');
            opt.value = c;
            opt.textContent = c;
            city.appendChild(opt);
        });
        result.textContent = '已选省份:' + MAP[val].name + ',请继续选择城市';
    } else {
        result.textContent = '请先选择省份';
    }
});

city.addEventListener('change', function(){
    var p = province.value;
    if (city.value && p && MAP[p]) {
        result.textContent = '当前选择:' + MAP[p].name + ' / ' + city.value;
    }
});
标签下拉多选 + 标签
Tag Dropdown
作用
以彩色标签展示已选项,强调视觉反馈,适合快速添加与移除。
适合场景
技能选择、兴趣标签、多维度筛选等。
AGENT 提示词
生成一个标签下拉,已选技能以彩色标签展示,支持点击移除。
<div class="dd dd-tags" id="tags">
    <div class="dd-toggle" role="button" tabindex="0">
        <span class="dd-pills"><span class="dd-placeholder">选择技能</span></span>
        <i class="fas fa-chevron-down dd-arrow"></i>
    </div>
    <ul class="dd-menu">
        <li data-value="js"><span class="dd-check is-hidden">✓</span> JavaScript</li>
        <li data-value="ts"><span class="dd-check is-hidden">✓</span> TypeScript</li>
        <li data-value="py"><span class="dd-check is-hidden">✓</span> Python</li>
        <li data-value="rust"><span class="dd-check is-hidden">✓</span> Rust</li>
        <li data-value="go"><span class="dd-check is-hidden">✓</span> Go</li>
    </ul>
</div>
.dd{position:relative;width:100%;min-width:190px;max-width:340px}
.dd .dd-toggle{
width:100%;padding:10px 16px;background:var(--ti-field);border:1.5px solid var(--ti-border);
border-radius:12px;font-size:.95rem;color:var(--ti-fg);cursor:pointer;display:flex;
justify-content:space-between;align-items:center;gap:8px;text-align:left;
transition:border-color .2s,background .2s;
}
.dd .dd-toggle:hover{border-color:var(--ti-accent);background:var(--ti-field-hover)}
.dd .dd-toggle .dd-arrow{transition:transform .2s;flex:none;font-size:.8rem;color:var(--ti-muted)}
.dd.is-open .dd-toggle .dd-arrow{transform:rotate(180deg)}
.dd .dd-menu{
position:absolute;top:calc(100% + 6px);left:0;width:100%;background:var(--ti-panel);
border-radius:14px;box-shadow:var(--ti-shadow);border:1px solid var(--ti-line);
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s,transform .2s,visibility .2s;z-index:100;
list-style:none;margin:0;padding:6px 0;overflow:hidden;
}
.dd.is-open .dd-menu{opacity:1;visibility:visible;transform:translateY(0)}
.dd .dd-menu li{
padding:10px 18px;cursor:pointer;transition:background .15s;font-size:.95rem;
color:var(--ti-fg);list-style:none;display:flex;align-items:center;gap:10px;
}
.dd .dd-menu li:hover{background:var(--ti-accent-ghost)}
.dd .dd-menu li.is-selected{background:var(--ti-accent-soft);font-weight:500;color:var(--ti-accent-deep)}
.dd .dd-placeholder{color:var(--ti-muted)}
.dd .dd-check{width:18px;text-align:center;color:var(--ti-accent);flex:none}
.dd .dd-check.is-hidden{opacity:0}
.dd-tags .dd-toggle{min-height:46px;flex-wrap:wrap}
.dd-tags .dd-pills{display:flex;flex-wrap:wrap;gap:5px;flex:1}
.dd-tags .dd-pill{
background:var(--ti-accent);color:#fff;padding:3px 9px 3px 13px;border-radius:30px;
font-size:.78rem;font-weight:500;display:inline-flex;align-items:center;gap:6px;
}
.dd-tags .dd-pill i{cursor:pointer;opacity:.85;font-size:.7rem;transition:opacity .2s}
.dd-tags .dd-pill i:hover{opacity:1}
.dd-tags .dd-menu{max-height:200px;overflow-y:auto}
function ddInit(root){
    var toggle = root.querySelector('.dd-toggle');
    var menu = root.querySelector('.dd-menu');
    if (!toggle || !menu) { return null; }

    function close(){ root.classList.remove('is-open'); }
    function open(){ root.classList.add('is-open'); }
    function isOpen(){ return root.classList.contains('is-open'); }

    toggle.addEventListener('click', function(e){
        e.stopPropagation();
        if (isOpen()) { close(); } else { open(); }
    });
    document.addEventListener('click', function(e){
        if (!root.contains(e.target)) { close(); }
    });
    document.addEventListener('keydown', function(e){
        if (e.key === 'Escape') { close(); }
    });

    return { root: root, toggle: toggle, menu: menu, open: open, close: close, isOpen: isOpen };
}
var dd = ddInit(document.getElementById('tags'));
if (dd) {
    var box = dd.root.querySelector('.dd-pills');
    var picked = [];

    function labelOf(val){
        var li = dd.menu.querySelector('li[data-value="' + val + '"]');
        return li ? li.textContent.trim() : val;
    }
    function render(){
        if (!picked.length) {
            box.innerHTML = '<span class="dd-placeholder">选择技能</span>';
            return;
        }
        box.innerHTML = picked.map(function(v){
            return '<span class="dd-pill">' + labelOf(v) +
                   ' <i class="fas fa-times" data-value="' + v + '"></i></span>';
        }).join('');
        box.querySelectorAll('.dd-pill i').forEach(function(icon){
            icon.addEventListener('click', function(e){
                e.stopPropagation();
                toggleVal(icon.getAttribute('data-value'));
            });
        });
    }
    function toggleVal(val){
        var i = picked.indexOf(val);
        var li = dd.menu.querySelector('li[data-value="' + val + '"]');
        var check = li ? li.querySelector('.dd-check') : null;
        if (i > -1) {
            picked.splice(i, 1);
            if (check) { check.classList.add('is-hidden'); }
        } else {
            picked.push(val);
            if (check) { check.classList.remove('is-hidden'); }
        }
        render();
    }

    dd.menu.querySelectorAll('li').forEach(function(li){
        li.addEventListener('click', function(e){
            e.stopPropagation();
            toggleVal(li.getAttribute('data-value'));
        });
    });
}
自定义下拉品牌定制
Custom Dropdown
作用
完全定制化样式与交互动效,满足品牌调性与特殊需求。
适合场景
用户菜单、品牌入口、特殊交互等。
AGENT 提示词
生成一个自定义风格的用户菜单下拉,带品牌色图标与展开动效。
<div class="dd dd-custom" id="custom">
    <button type="button" class="dd-toggle">
        <span class="dd-face">
            <span class="dd-icon"><i class="fas fa-user"></i></span>
            <span class="dd-label">用户菜单</span>
        </span>
        <i class="fas fa-chevron-down dd-arrow"></i>
    </button>
    <ul class="dd-menu">
        <li data-value="profile"><i class="fas fa-user-circle"></i> 个人中心</li>
        <li data-value="setting"><i class="fas fa-cog"></i> 设置</li>
        <li data-value="star"><i class="fas fa-star"></i> 收藏</li>
        <li data-value="logout"><i class="fas fa-sign-out-alt"></i> 退出</li>
    </ul>
</div>
.dd{position:relative;width:100%;min-width:190px;max-width:340px}
.dd .dd-toggle{
width:100%;padding:10px 16px;background:var(--ti-field);border:1.5px solid var(--ti-border);
border-radius:12px;font-size:.95rem;color:var(--ti-fg);cursor:pointer;display:flex;
justify-content:space-between;align-items:center;gap:8px;text-align:left;
transition:border-color .2s,background .2s;
}
.dd .dd-toggle:hover{border-color:var(--ti-accent);background:var(--ti-field-hover)}
.dd .dd-toggle .dd-arrow{transition:transform .2s;flex:none;font-size:.8rem;color:var(--ti-muted)}
.dd.is-open .dd-toggle .dd-arrow{transform:rotate(180deg)}
.dd .dd-menu{
position:absolute;top:calc(100% + 6px);left:0;width:100%;background:var(--ti-panel);
border-radius:14px;box-shadow:var(--ti-shadow);border:1px solid var(--ti-line);
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s,transform .2s,visibility .2s;z-index:100;
list-style:none;margin:0;padding:6px 0;overflow:hidden;
}
.dd.is-open .dd-menu{opacity:1;visibility:visible;transform:translateY(0)}
.dd .dd-menu li{
padding:10px 18px;cursor:pointer;transition:background .15s;font-size:.95rem;
color:var(--ti-fg);list-style:none;display:flex;align-items:center;gap:10px;
}
.dd .dd-menu li:hover{background:var(--ti-accent-ghost)}
.dd .dd-menu li.is-selected{background:var(--ti-accent-soft);font-weight:500;color:var(--ti-accent-deep)}
.dd .dd-placeholder{color:var(--ti-muted)}
.dd .dd-check{width:18px;text-align:center;color:var(--ti-accent);flex:none}
.dd .dd-check.is-hidden{opacity:0}
.dd-custom .dd-toggle{
border-radius:40px;border-width:2px;font-weight:500;
box-shadow:0 2px 8px rgba(84,164,243,.1);
}
.dd-custom .dd-toggle:hover{
border-color:var(--ti-accent);box-shadow:0 4px 16px rgba(84,164,243,.22);
}
.dd-custom .dd-face{display:inline-flex;align-items:center;gap:10px}
.dd-custom .dd-icon{
background:var(--ti-accent);color:#fff;border-radius:50%;width:26px;height:26px;
display:inline-flex;align-items:center;justify-content:center;font-size:.8rem;flex:none;
}
.dd-custom .dd-menu{border-radius:22px;padding:8px 0;transform:translateY(-8px) scale(.96)}
.dd-custom.is-open .dd-menu{transform:translateY(0) scale(1)}
.dd-custom .dd-menu li{
padding:12px 20px;gap:12px;border-left:3px solid transparent;
transition:background .15s,padding-left .2s,border-color .2s;
}
.dd-custom .dd-menu li:hover{
background:var(--ti-accent-ghost);border-left-color:var(--ti-accent);padding-left:26px;
}
.dd-custom .dd-menu li i{width:22px;color:var(--ti-accent);flex:none}
function ddInit(root){
    var toggle = root.querySelector('.dd-toggle');
    var menu = root.querySelector('.dd-menu');
    if (!toggle || !menu) { return null; }

    function close(){ root.classList.remove('is-open'); }
    function open(){ root.classList.add('is-open'); }
    function isOpen(){ return root.classList.contains('is-open'); }

    toggle.addEventListener('click', function(e){
        e.stopPropagation();
        if (isOpen()) { close(); } else { open(); }
    });
    document.addEventListener('click', function(e){
        if (!root.contains(e.target)) { close(); }
    });
    document.addEventListener('keydown', function(e){
        if (e.key === 'Escape') { close(); }
    });

    return { root: root, toggle: toggle, menu: menu, open: open, close: close, isOpen: isOpen };
}
var dd = ddInit(document.getElementById('custom'));
if (dd) {
    var label = dd.root.querySelector('.dd-label');
    var icon = dd.root.querySelector('.dd-icon');
    dd.menu.querySelectorAll('li').forEach(function(li){
        li.addEventListener('click', function(e){
            e.stopPropagation();
            label.textContent = li.textContent.trim();
            var picked = li.querySelector('i');
            if (picked && icon) { icon.innerHTML = '<i class="' + picked.className + '"></i>'; }
            dd.menu.querySelectorAll('li').forEach(function(x){ x.classList.remove('is-selected'); });
            li.classList.add('is-selected');
            dd.close();
        });
    });
}