在 Svelte 中使用 Melt UI 构建可折叠组件

发布: (2026年1月6日 GMT+8 16:50)
8 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文。

Melt UI 可折叠指南

Melt UI 为 Svelte 提供了遵循 WAI‑ARIA 指南的无 UI、可访问的组件构建器。Collapsible 构建器让您能够开箱即用地创建可展开/折叠的内容区域,并具备完整的键盘导航和屏幕阅读器支持。

本指南通过实际的、可用于生产环境的实现,演示如何使用 Melt UI 的 createCollapsible 构建器创建可折叠组件。重点在于真实的使用模式,而非抽象概念。

为什么在 @melt-ui/svelte(Melt UI)中使用 Svelte?

Svelte 应用经常需要可展开的内容区域,用于 FAQ、手风琴、导航菜单和详情视图。Melt UI 的 Collapsible 构建器自然契合此生态,因为它提供:

  • 完整的 WAI‑ARIA 合规性,确保可访问性
  • Headless 设计(由你自行控制所有样式)
  • TypeScript 支持,具备出色的类型安全
  • 简单、声明式的 API
  • 内置键盘导航
  • 除 Svelte 外无其他外部依赖

构建器模式允许你将可折叠行为附加到任意元素上,完全掌控标记和样式。

要求

  • 一个 Svelte 项目(SvelteKit 或独立的 Svelte)
  • Node.js 16+ 以及 npm/pnpm/yarn
  • 对 Svelte 组件和 store 有基本了解

安装

使用您喜欢的包管理器安装 Melt UI:

npm install @melt-ui/svelte
# or
pnpm add @melt-ui/svelte
# or
yarn add @melt-ui/svelte

该包包括所有构建器及其 TypeScript 定义。

配置

无需额外配置。Melt UI 可直接在 Svelte 中使用。如果您使用 TypeScript,请确保您的 tsconfig.json 包含正确的模块解析:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "target": "ES2020"
  }
}

基本用法

创建可折叠区域的最简方式是使用 createCollapsible 构建器:

<script>
  import { createCollapsible, melt } from '@melt-ui/svelte';

  const {
    elements: { root, content, trigger },
    states: { open }
  } = createCollapsible();
</script>

<div use:melt={root}>
  <button use:melt={trigger}>
    {$open ? 'Close' : 'Open'}
  </button>

  <div use:melt={content}>
    This content can be expanded and collapsed.
  </div>
</div>

createCollapsible() 返回内容

  • elements – 用于 rootcontenttrigger 元素的 Svelte store。
  • states – 如 open 之类的响应式 store,用于跟踪折叠状态。

使用 melt 动作将构建器的行为附加到你的元素上。open store 是响应式的,当折叠状态变化时会自动更新。

高级功能

使用 Tailwind CSS 进行样式

Since Melt UI is headless, you have complete control over styling:

<script>
  import { createCollapsible, melt } from '@melt-ui/svelte';

  const {
    elements: { root, content, trigger },
    states: { open }
  } = createCollapsible();
</script>

<div use:melt={root} class="border rounded">
  <button use:melt={trigger} class="px-4 py-2 bg-gray-200">
    Toggle Content <span class="ml-2">▼</span>
  </button>

  <div use:melt={content} class="p-4">
    This is the collapsible content area.
  </div>
</div>

受控状态

To control the collapsible state externally, pass a writable store:

<script>
  import { createCollapsible, melt } from '@melt-ui/svelte';
  import { writable } from 'svelte/store';

  const isOpen = writable(false);

  const {
    elements: { root, content, trigger },
    states: { open }
  } = createCollapsible({ open: isOpen });

  function toggle() {
    isOpen.update(n => !n);
  }
</script>

<div use:melt={root}>
  <button use:melt={trigger} on:click={toggle}>
    {$open ? 'Close' : 'Open'}
  </button>

  <div use:melt={content}>
    Controlled content
  </div>
</div>

创建手风琴

Build an accordion by managing multiple collapsible states:

<script>
  import { createCollapsible, melt } from '@melt-ui/svelte';
  import { writable } from 'svelte/store';

  const openIndex = writable(null);

  const items = [
    { id: 1, title: 'Item 1', content: 'Content for item 1' },
    { id: 2, title: 'Item 2', content: 'Content for item 2' },
    { id: 3, title: 'Item 3', content: 'Content for item 3' }
  ];

  function createItemCollapsible(index) {
    const isOpen = writable(openIndex.get() === index);

    const unsubscribe = openIndex.subscribe(value => {
      isOpen.set(value === index);
    });

    return {
      collapsible: createCollapsible({ open: isOpen }),
      unsubscribe
    };
  }
</script>

{#each items as item, index}
  {@const { collapsible, unsubscribe } = createItemCollapsible(index)}
  {@const { elements: { root, content, trigger }, states: { open } } = collapsible}

  <div use:melt={root} class="border-b">
    <button
      use:melt={trigger}
      on:click={() => openIndex.set($open ? null : index)}
      class="w-full px-4 py-2 text-left"
    >
      {item.title}
    </button>

    {#if $open}
      <div use:melt={content} class="p-4">
        {item.content}
      </div>
    {/if}
  </div>
{/each}

自定义动画

Add animations using Svelte’s transition directives:

<script>
  import { createCollapsible, melt } from '@melt-ui/svelte';
  import { slide } from 'svelte/transition';

  const {
    elements: { root, content, trigger },
    states: { open }
  } = createCollapsible();
</script>

<div use:melt={root}>
  <button use:melt={trigger}>
    {$open ? 'Hide' : 'Show'} details
  </button>

  {#if $open}
    <div use:melt={content} transition:slide>
      Animated collapsible content.
    </div>
  {/if}
</div>

结论

Melt UI 的无头、可访问的 Collapsible 构建器为您提供灵活性,以在 Svelte 中创建完全自定义、符合 ARIA 标准的可展开部分。通过利用构建器模式,您可以完全控制标记、样式和状态管理,同时受益于内置的可访问性和键盘支持。祝编码愉快!

<script>
  import { createCollapsible, melt } from '@melt-ui/svelte';
  import { slide } from 'svelte/transition';

  const {
    elements: { root, content, trigger },
    states: { open }
  } = createCollapsible();
</script>

<div use:melt={root}>
  <button use:melt={trigger}>Toggle</button>

  {#if $open}
    <div use:melt={content} transition:slide>
      This content slides in and out.
    </div>
  {/if}
</div>

全屏控制

  • 进入全屏模式
  • 退出全屏模式

常见问题 / 故障排除

折叠面板不切换

  • 确保 melt 动作已应用于 三个元素roottriggercontent)。
  • 检查存储是否使用 $ 前缀访问:$open
  • 确认构建器在组件初始化时 只调用一次(不要有条件调用)。

缺少可访问性属性

  • 确保 melt 动作已应用于元素,并且没有因条件渲染而被移除。
  • 不要 手动添加 ARIA 属性——构建器会自动处理。

TypeScript 错误

  • 需要时从 @melt-ui/svelte 导入类型。
  • 使用 Svelte v4+ 以获得最佳 TypeScript 支持。

内容未动画

  • 使用 {#if} 条件渲染时,确保 melt 动作仍然被应用。
  • 将 Svelte 过渡(例如 transition:slide)应用于 content 元素,而不是根元素。

生产最佳实践

  • 始终在 所有构建器元素 上使用 melt 操作,以确保可访问性。
  • 测试键盘导航(TabEnterSpaceEscape)。
  • 使用屏幕阅读器验证 ARIA 属性。
  • 将样式与逻辑分离——Melt UI 负责行为,您负责外观。
  • 为获得更佳性能,优先使用 CSS 过渡而非 JavaScript 动画。
  • 对于手风琴组件,一次只允许 一个项目 打开,以提升用户体验。
  • 在组件销毁时清理订阅,以避免内存泄漏。

最后说明

Melt UI 的 Collapsible 构建器为可展开内容提供了坚实的基础,设置简便。它的 headless 方式让您能够完全控制样式,同时保持可访问性合规。

一旦 collapsible 正常工作,常见的后续步骤包括:

  • 使用共享状态构建 accordion 组件。
  • 添加自定义动画和过渡效果。
  • 与路由集成,实现可展开的导航菜单。
  • 创建带有可折叠区段的详细视图。
Back to Blog

相关文章

阅读更多 »

实践中的可访问性

♿ 初学者指南:数字无障碍远不止遵守技术规则。它旨在包容人们,确保自主性,并让所有人能够……

AI-slop 已充斥模板市场

!AI‑slop 的封面图片已充斥模板市场 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F...

介绍 graphql-complexity-validation

✨ 特性 - 零运行时依赖 - 完全类型化的 TypeScript - 支持 fragments 与 inline fragments - 默认忽略 Introspection 适用于: - Apollo…