Skip to content

Dropdown 下拉菜单

实时预览

引入

html
<link rel="stylesheet" href="/tokens/tokens.css">
<link rel="stylesheet" href="/components/dropdown/dropdown.css">

代码

HTML

html
<!-- Dropdown Component — 2 variants: default, split -->


<!-- Demo: Dropdown 组件 -->
<div style="display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; padding: 20px;">
  
  <!-- Default Dropdown -->
  <div class="dropdown">
    <button class="dropdown__trigger">
      操作菜单
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <polyline points="6 9 12 15 18 9"></polyline>
      </svg>
    </button>
    <div class="dropdown__menu">
      <div class="dropdown__group">
        <div class="dropdown__group-title">用户操作</div>
        <a href="#" class="dropdown__item">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
            <circle cx="12" cy="7" r="4"></circle>
          </svg>
          个人资料
        </a>
        <a href="#" class="dropdown__item">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <circle cx="12" cy="12" r="3"></circle>
            <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
          </svg>
          设置
        </a>
      </div>
      <div class="dropdown__divider"></div>
      <div class="dropdown__group">
        <a href="#" class="dropdown__item dropdown__item--danger">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
            <polyline points="16 17 21 12 16 7"></polyline>
            <line x1="21" y1="12" x2="9" y2="12"></line>
          </svg>
          退出登录
        </a>
      </div>
    </div>
  </div>

  <!-- Split Dropdown -->
  <div class="dropdown dropdown--split">
    <button class="dropdown__trigger">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M12 5v14M5 12h14"></path>
      </svg>
      新建项目
    </button>
    <button class="dropdown__trigger dropdown__split-trigger">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <polyline points="6 9 12 15 18 9"></polyline>
      </svg>
    </button>
    <div class="dropdown__menu">
      <a href="#" class="dropdown__item">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
          <line x1="3" y1="9" x2="21" y2="9"></line>
          <line x1="9" y1="21" x2="9" y2="9"></line>
        </svg>
        创建文件夹
      </a>
      <a href="#" class="dropdown__item">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
          <polyline points="14 2 14 8 20 8"></polyline>
          <line x1="12" y1="18" x2="12" y2="12"></line>
          <line x1="9" y1="15" x2="15" y2="15"></line>
        </svg>
        创建文件
      </a>
      <a href="#" class="dropdown__item">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <polyline points="16 18 22 12 16 6"></polyline>
          <polyline points="8 6 2 12 8 18"></polyline>
        </svg>
        从模板创建
      </a>
    </div>
  </div>

  <!-- Right-aligned Dropdown -->
  <div class="dropdown">
    <button class="dropdown__trigger">
      右对齐菜单
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <polyline points="6 9 12 15 18 9"></polyline>
      </svg>
    </button>
    <div class="dropdown__menu dropdown__menu--right">
      <a href="#" class="dropdown__item">选项一</a>
      <a href="#" class="dropdown__item">选项二</a>
      <a href="#" class="dropdown__item">选项三</a>
    </div>
  </div>

</div>

CSS

css
/* Dropdown.css */
.dropdown {
  position: relative;
  display: inline-block;
  font-family: var(--font-sans);
}

/* ── 触发按钮 ──────────────────────────── */
.dropdown__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 10px 22px;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1.5px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--duration-normal) var(--ease-default);
  user-select: none;
  white-space: nowrap;
}

.dropdown__trigger:hover {
  border-color: var(--color-border-hover);
  background: var(--color-bg-subtle);
}

.dropdown__trigger:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

.dropdown__trigger svg {
  width: 16px;
  height: 16px;
  transition: transform var(--duration-normal) var(--ease-default);
}

/* ── 菜单 ──────────────────────────── */
.dropdown__menu {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  min-width: 200px;
  max-width: 320px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-2);
  z-index: var(--z-dropdown);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: all var(--duration-normal) var(--ease-default);
  transform-origin: top left;
}

.dropdown__menu--right {
  left: auto;
  right: 0;
  transform-origin: top right;
}

/* ── 显示菜单 ──────────────────────────── */
.dropdown:focus-within .dropdown__menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.dropdown:focus-within .dropdown__trigger svg {
  transform: rotate(180deg);
}

/* ── 菜单项 ──────────────────────────── */
.dropdown__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  color: var(--color-text);
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: all var(--duration-fast) var(--ease-default);
  cursor: pointer;
  font-size: var(--text-base);
  line-height: 1;
}

.dropdown__item:hover {
  background: var(--color-bg-muted);
  color: var(--color-text);
}

.dropdown__item:active {
  background: var(--color-primary-ghost);
}

.dropdown__item--danger {
  color: var(--color-danger);
}

.dropdown__item--danger:hover {
  background: rgba(239, 68, 68, 0.08);
  color: var(--color-danger);
}

.dropdown__item svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--color-text-muted);
}

.dropdown__item--danger svg {
  color: var(--color-danger);
}

/* ── 分隔线 ──────────────────────────── */
.dropdown__divider {
  height: 1px;
  background: var(--color-border);
  margin: var(--space-2) 0;
}

/* ── 分组 ──────────────────────────── */
.dropdown__group {
  margin-bottom: var(--space-2);
}

.dropdown__group:last-child {
  margin-bottom: 0;
}

.dropdown__group-title {
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ── Split 变体 ──────────────────────────── */
.dropdown--split {
  display: inline-flex;
}

.dropdown--split .dropdown__trigger {
  border-radius: var(--radius-md) 0 0 var(--radius-md);
  border-right: none;
}

.dropdown--split .dropdown__trigger:last-child,
.dropdown--split .dropdown__split-trigger {
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  border-right: 1.5px solid var(--color-border);
  padding: 10px 14px;
}

.dropdown--split .dropdown__split-trigger {
  border-left: none;
}

.dropdown--split .dropdown__menu {
  left: auto;
  right: 0;
  transform-origin: top right;
}

/* ── 暗黑模式适配 ──────────────────────────── */
[data-theme="dark"] .dropdown__trigger {
  background: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border);
}

[data-theme="dark"] .dropdown__trigger:hover {
  background: var(--color-bg-subtle);
  border-color: var(--color-border-hover);
}

[data-theme="dark"] .dropdown__menu {
  background: var(--color-surface);
  border-color: var(--color-border);
  box-shadow: var(--shadow-xl);
}

[data-theme="dark"] .dropdown__item {
  color: var(--color-text);
}

[data-theme="dark"] .dropdown__item:hover {
  background: var(--color-bg-muted);
  color: var(--color-text);
}

[data-theme="dark"] .dropdown__item--danger {
  color: var(--color-danger);
}

[data-theme="dark"] .dropdown__item--danger:hover {
  background: rgba(239, 68, 68, 0.15);
}

[data-theme="dark"] .dropdown__divider {
  background: var(--color-border);
}

[data-theme="dark"] .dropdown__group-title {
  color: var(--color-text-muted);
}

AI 使用说明

组件名: dropdown
选择器: .dropdown
依赖: /tokens/tokens.css
文件: /components/dropdown/dropdown.html

纯 HTML + CSS,零框架依赖,AI 友好