
shadcn-svelte 2024 年 1 月新增组件解析Carousel、Drawer、Sonner 与 Pagination 实战指南【免费下载链接】shadcn-svelteshadcn/ui, but for Svelte. ✨项目地址: https://gitcode.com/GitHub_Trending/sh/shadcn-svelte2024 年 1 月shadcn-svelte 一次性加入了四个新组件Carousel、Drawer、Sonner 与 Pagination覆盖了轮播、抽屉、轻提示与分页四类高频交互场景。本文以 2024-01-new-components.md 为骨架结合仓库中各组件在 docs/src/lib/registry/ui 下的真实源码实现逐一对四个组件的安装、用法、关键 API 与底层原理展开说明读完即可在 SvelteKit 项目中直接落地使用。一、背景一次覆盖四类交互的组件扩充按 2024-01-new-components.md 的记录本次新增的四个组件分别依赖不同的底层库组件底层实现定位CarouselEmbla Carousel支持手势滑动与动画的轮播Drawervaul-svelte可拖拽的抽屉式面板Sonnersvelte-sonner偏执风格的 toast 轻提示PaginationBits UI 的 Pagination 组件分页导航其中 Drawer 与 Sonner 的原始实现均出自 Emil Kowalski先在 React 生态中诞生Vaul 与 Sonner再被移植到 Svelte。这种“以成熟开源库为底座、对外统一 shadcn 风格 API”的路线与 shadcn-svelte 整体“可复制、可定制”的设计哲学保持一致。二、Carousel基于 Embla 的轮播组件2.1 组件构成与源码结构Carousel 的注册表源码位于 docs/src/lib/registry/ui/carousel共 7 个文件carousel.svelteRoot即Carousel.Rootcarousel-content.svelteCarousel.Content可滚动视口carousel-item.svelteCarousel.Item单个条目carousel-previous.svelte/carousel-next.svelte前后翻页按钮context.tsContext 与类型定义index.ts命名导出入口从 context.ts 可以看到 Root 的核心 propsexport type CarouselProps { opts?: CarouselOptions; // Embla 配置见下方 Options 小节 plugins?: CarouselPlugins; // Embla 插件数组 setApi?: (api: CarouselAPI | undefined) void; // 暴露 Embla API orientation?: horizontal | vertical; // 默认 horizontal } WithElementRefHTMLAttributesHTMLDivElement;Root 组件通过 Svelte 的 Context 机制setEmblaContext/getEmblaContext见 context.ts把 API 实例与滚动方法共享给所有子组件子组件若脱离Carousel.Root单独使用会抛出运行时错误提示。carousel.svelte中 Root 的键盘行为也值得注意carousel.svelte左右方向键分别触发scrollPrev/scrollNext即开箱即用的无障碍键盘导航并通过roleregion与aria-roledescriptioncarousel声明语义。2.2 安装通过 CLI 安装npx shadcn-sveltelatest add carousel手动安装则先安装依赖再复制源码npm install embla-carousel-svelte -D复制 docs/src/lib/registry/ui/carousel 下的全部文件到项目的$lib/components/ui/carousel目录即可。2.3 基础用法script langts import * as Carousel from $lib/components/ui/carousel/index.js; /script Carousel.Root Carousel.Content Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item /Carousel.Content Carousel.Previous / Carousel.Next / /Carousel.Root2.4 尺寸控制用 Tailwind 的 basis 工具类条目的宽度不需要专门 prop直接在Carousel.Item /上使用basis系列工具类即可!-- 每项占轮播宽度的 33% -- Carousel.Root Carousel.Content Carousel.Item classbasis-1/3.../Carousel.Item Carousel.Item classbasis-1/3.../Carousel.Item Carousel.Item classbasis-1/3.../Carousel.Item /Carousel.Content /Carousel.Root!-- 小屏每项 50%大屏每项 33% -- Carousel.Root Carousel.Content Carousel.Item classmd:basis-1/2 lg:basis-1/3.../Carousel.Item Carousel.Item classmd:basis-1/2 lg:basis-1/3.../Carousel.Item Carousel.Item classmd:basis-1/2 lg:basis-1/3.../Carousel.Item /Carousel.Content /Carousel.Root2.5 间距控制Content 负边距 Item 内边距间距通过“外边距抵消”实现Carousel.Content /加负的-ms-[VALUE]margin-inline-start每个Carousel.Item /加正向的ps-[VALUE]padding-inline-start两者数值保持一致Carousel.Root Carousel.Content class-ms-4 Carousel.Item classps-4.../Carousel.Item Carousel.Item classps-4.../Carousel.Item Carousel.Item classps-4.../Carousel.Item /Carousel.Content /Carousel.Root也可组合响应式断点让间距在不同屏幕下变化Carousel.Root Carousel.Content class-ms-2 md:-ms-4 Carousel.Item classps-2 md:ps-4.../Carousel.Item Carousel.Item classps-2 md:ps-4.../Carousel.Item Carousel.Item classps-2 md:ps-4.../Carousel.Item /Carousel.Content /Carousel.Root2.6 方向horizontal 与 vertical通过orientationprop 切换横竖方向可选值为horizontal | vertical默认horizontalCarousel.Root orientationvertical Carousel.Content Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item /Carousel.Content /Carousel.Root2.7 Options直接透传 Embla 配置所有 Embla 的配置项都可以通过optsprop 传入例如对齐方式与循环模式Carousel.Root opts{{ align: start, loop: true, }} Carousel.Content Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item /Carousel.Content /Carousel.Rootopts的类型直接取自 Embla 的配置类型context.ts因此所有 Embla 选项align、loop、startIndex、containScroll、dragFree等都获得完整类型提示。其余选项可查阅 Embla Carousel 官方文档。2.8 API用 setApi 拿到 Embla 实例需要程序化控制如联动当前页码时通过setApi回调捕获 Embla API 实例。结合 Svelte 5 的 runes 写法script langts import { type CarouselAPI } from $lib/components/ui/carousel/context.js; import * as Carousel from $lib/components/ui/carousel/index.js; let api $stateCarouselAPI(); let current $state(0); const count $derived(api ? api.scrollSnapList().length : 0); $effect(() { if (api) { current api.selectedScrollSnap() 1; api.on(select, () { current api!.selectedScrollSnap() 1; }); } }); /script Carousel.Root setApi{(emblaApi) (api emblaApi)} Carousel.Content Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item /Carousel.Content /Carousel.Root在源码层面setApi是在onInit中触发的carousel.svelteRoot 监听 Embla 的emblaInit事件把event.detail存入 context 中的api字段同时调用setApi、填充scrollSnaps数组并注册select监听实时同步selectedIndex、canScrollNext、canScrollPrev。组件卸载时$effect的清理函数会调用api.off(select, onSelect)解除监听carousel.svelte避免内存泄漏。2.9 Events监听滚动事件同样借助 API 实例订阅事件例如selectscript langts import { type CarouselAPI } from $lib/components/ui/carousel/context.js; import * as Carousel from $lib/components/ui/carousel/index.js; let api $stateCarouselAPI(); $effect(() { if (api) { api.on(select, () { // 当前滑动位置变化时触发 }); } }); /script Carousel.Root setApi{(emblaApi) (api emblaApi)} Carousel.Content Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item Carousel.Item.../Carousel.Item /Carousel.Content /Carousel.Root2.10 Plugins接入 Embla 插件生态通过pluginsprop 传入插件数组例如自动播放script langts import Autoplay from embla-carousel-autoplay; import * as Carousel from $lib/components/ui/carousel/index.js; /script Carousel.Root plugins{[ Autoplay({ delay: 2000, }), ]} !-- ... -- /Carousel.Rootplugins的类型同样取自 Emblacontext.tsEmbla 的 ClassNames、AutoScroll、AutoHeight、DragScroll、Parallax、Zoom 等插件均可按官方文档接入。三、Drawer基于 vaul-svelte 的抽屉组件3.1 组件构成与源码结构Drawer 的注册表源码位于 docs/src/lib/registry/ui/drawer共 12 个文件拆分为Root、Trigger、Content、Overlay、Portal、Header、Footer、Title、Description、Close、Nested等子组件分别对应drawer.svelte、drawer-trigger.svelte、drawer-content.svelte等文件。组件建立在 vaul-svelte 之上后者是 Emil Kowalski 为 React 编写的 Vaul 的 Svelte 移植版提供可拖拽、带弹簧动画的抽屉体验。3.2 安装通过 CLI 安装npx shadcn-sveltelatest add drawer手动安装npm install vaul-sveltenext -D再复制 docs/src/lib/registry/ui/drawer 下全部文件到项目的$lib/components/ui/drawer目录。3.3 基础用法script langts import * as Drawer from $lib/components/ui/drawer/index.js; /script Drawer.Root Drawer.TriggerOpen/Drawer.Trigger Drawer.Content Drawer.Header Drawer.TitleAre you sure absolutely sure?/Drawer.Title Drawer.DescriptionThis action cannot be undone./Drawer.Description /Drawer.Header Drawer.Footer ButtonSubmit/Button Drawer.CloseCancel/Drawer.Close /Drawer.Footer /Drawer.Content /Drawer.RootDrawer.Title与Drawer.Description承担无障碍语义角色Drawer.Close直接关闭抽屉结构与 Dialog 高度一致迁移成本低。3.4 Sides四种弹出方向通过directionprop 控制抽屉从哪一侧滑入可选top、right、bottom、leftDrawer.Root directionright Drawer.TriggerOpen from right/Drawer.Trigger Drawer.Content !-- ... -- /Drawer.Content /Drawer.Root3.5 Responsive Dialog桌面用 Dialog、移动端用 DrawerDrawer 与 Dialog 可以组合出经典的响应式方案——桌面端渲染居中对话框移动端渲染底部抽屉。判断逻辑用断点 hook 或媒体查询区分屏幕尺寸在桌面分支渲染Dialog.Root、在移动端分支渲染Drawer.Root即可两者的子组件结构Trigger / Content / Header / Title / Description / Footer / Close几乎一一对应切换成本极低。四、Sonner偏执风格的 toast 轻提示4.1 组件构成与源码结构Sonner 的注册表源码位于 docs/src/lib/registry/ui/sonner仅两个文件sonner.svelteToaster /本体与index.ts。组件由 svelte-sonner 提供后者是 Emil Kowalski 为 React 编写的 Sonner 的 Svelte 移植版。从 sonner.svelte 可以看到仓库当前实现的关键细节theme{mode.current}主题跟随 mode-watcher 的当前模式通过 CSS 变量把 toast 配色接入主题系统--normal-bg: var(--color-popover)、--normal-text: var(--color-popover-foreground)、--normal-border: var(--color-border)通过loadingIcon/successIcon/errorIcon/infoIcon/warningIcon五个 snippet 覆盖默认状态图标加载旋转图标、成功、错误、信息、警告。4.2 安装与主题支持CLI 方式安装npx shadcn-sveltelatest add sonner然后安装 mode-watcher 以支持亮暗主题跟随如已有则跳过npm install mode-watcher默认情况下 Sonner 使用操作系统的明暗偏好决定主题要让主题与应用保持一致有两种做法传入自定义themeprop 覆盖使用 mode-watcher可直接硬编码为dark或light。如果希望完全退出深色模式支持可在 CLI 安装后卸载mode-watcher并移除组件中的themeprop或手动安装组件时就不引入 mode-watcher。主题接入的详细配置可参考 dark-mode/index.md。CLI 安装完成后把Toaster /放到根布局中!-- src/routes/layout.svelte -- script langts import { Toaster } from $lib/components/ui/sonner/index.js; let { children } $props(); /script Toaster / {render children?.()}手动安装则先装依赖再复制源码npm install svelte-sonner -D复制 docs/src/lib/registry/ui/sonner 下全部文件到$lib/components/ui/sonner并按上面的方式在布局中加入Toaster /。4.3 用法随处触发 toasttoast函数从svelte-sonner直接导入可放在任意组件中script langts import { toast } from svelte-sonner; import { Button } from $lib/components/ui/button/index.js; /script Button onclick{() toast(Hello world)}Show toast/Button成功、错误、加载等变体由toast.success()、toast.error()、toast.loading()等方法提供具体类型预览可查看文档站中sonner-types示例。4.4 Changelog2025 年 12 月图标更新Sonner 组件后续迭代中默认状态图标已统一改为 lucide 图标。若你的项目还在使用旧版图标可参考以下更新方式sonner.sveltescript langts import CircleCheckIcon from lucide/svelte/icons/circle-check; import InfoIcon from lucide/svelte/icons/info; import Loader2Icon from lucide/svelte/icons/loader-2; import OctagonXIcon from lucide/svelte/icons/octagon-x; import TriangleAlertIcon from lucide/svelte/icons/triangle-alert; import { Toaster as Sonner, type ToasterProps as SonnerProps, } from svelte-sonner; import { mode } from mode-watcher; let { ...restProps }: SonnerProps $props(); /script Sonner theme{mode.current} classtoaster group style--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border); {...restProps} {#snippet loadingIcon()} Loader2Icon classsize-4 animate-spin / {/snippet} {#snippet successIcon()} CircleCheckIcon classsize-4 / {/snippet} {#snippet errorIcon()} OctagonXIcon classsize-4 / {/snippet} {#snippet infoIcon()} InfoIcon classsize-4 / {/snippet} {#snippet warningIcon()} TriangleAlertIcon classsize-4 / {/snippet} /Sonner五、Pagination基于 Bits UI 的分页组件5.1 组件构成与源码结构Pagination 的注册表源码位于 docs/src/lib/registry/ui/pagination共 10 个文件pagination.svelteRoot、pagination-content.svelte、pagination-item.svelte、pagination-link.svelte、pagination-ellipsis.svelte、pagination-previous.svelte、pagination-next.svelte及对应的按钮变体文件、index.ts。组件直接复用了 Bits UI 的 Pagination 原语并叠加 shadcn 风格的样式。Root 的关键 props 定义在 pagination.sveltelet { ref $bindable(null), class: className, count 0, // 总条目数 perPage 10, // 每页条数默认 10 page $bindable(1), // 当前页码双向绑定默认 1 siblingCount 1, // 当前页两侧显示的页码数量 ...restProps }: PaginationPrimitive.RootProps $props();其中page是$bindable的意味着父组件可以通过bind:page双向同步当前页count与perPage决定总页数siblingCount控制省略号出现前的页码稠密度。5.2 安装通过 CLI 安装npx shadcn-sveltelatest add pagination手动安装则先安装依赖再复制源码npm install bits-ui -D复制 docs/src/lib/registry/ui/pagination 下全部文件到$lib/components/ui/pagination目录。5.3 用法children snippet 渲染页码Pagination 采用 snippet 驱动的方式渲染页码childrensnippet 接收{ pages, currentPage }由你决定如何绘制每个页码与省略号script langts import * as Pagination from $lib/components/ui/pagination/index.js; /script Pagination.Root count{100} perPage{10} {#snippet children({ pages, currentPage })} Pagination.Content Pagination.Item Pagination.Previous / /Pagination.Item {#each pages as page (page.key)} {#if page.type ellipsis} Pagination.Item Pagination.Ellipsis / /Pagination.Item {:else} Pagination.Item Pagination.Link {page} isActive{currentPage page.value} {page.value} /Pagination.Link /Pagination.Item {/if} {/each} Pagination.Item Pagination.Next / /Pagination.Item /Pagination.Content {/snippet} /Pagination.Rootpages数组中的每个元素带key用作{#each}的唯一键、typeellipsis或普通页码与valueisActive用于高亮当前页。Root 自身带有rolenavigation与aria-labelpagination语义。需要受控分页时用bind:page将当前页与外部状态联动即可。六、小结四组件背后的共性设计从 docs/src/lib/registry/ui 目录的源码结构可以看出这四类组件的实现方式高度统一底层复用成熟库Carousel 复用 Embla Carousel SvelteDrawer 复用 vaul-svelteSonner 复用 svelte-sonnerPagination 复用 Bits UI各自继承其完整能力与生态对外统一 shadcn 风格 API组件以index.ts命名空间导出 子组件切片Root/Content/Item等组织安装、复制、样式定制方式与其他 shadcn-svelte 组件完全一致深度融入主题与无障碍Sonner 通过 CSS 变量接入 popover 色板并跟随 mode-watcherCarousel 提供键盘方向键与 ARIA 角色Pagination 自带导航语义Svelte 5 runes 支持源码大量使用$state、$derived、$effect、$bindable与 snippet与 SvelteKit 最新版本协同工作。四个组件的详细 API 与更多示例可分别在 carousel.md、drawer.md、sonner.md 与 pagination.md 中继续查阅。【免费下载链接】shadcn-svelteshadcn/ui, but for Svelte. ✨项目地址: https://gitcode.com/GitHub_Trending/sh/shadcn-svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考