Skip to content

NavigationRail 侧边导航栏 ​

竖向导航。NavigationRail 持有选中值(v-model:value),NavigationRailItem 描述每一项。 同一应用里导航栏应当是唯一的导航元素,不要再叠加 NavigationBar / NavigationDrawer。

折叠态固定 96px,展开态 220–360px,两种形态都始终显示文字。

形态宽度排版
折叠(默认,96px)固定 96px图标在上、文字在下
展开(expandable)220–360px图标与文字左右排列,整行胶囊指示
不可展开(无 expandable)固定 96px始终折叠排版,v-model:expanded 不起作用

子项约定 ​

NavigationRail / NavigationRailItem 遵循父子组件以及插槽统一范式:选中值由 v-model:value 持有,NavigationRailItem 用 label、icon 或默认插槽描述内容,标识取 value 或 父组件注入的索引。

标准侧边导航 ​

不传 expandable 时是固定 96px 的折叠 rail:图标在上、文字在下,v-model:expanded 不会改变宽度。

应用内容

当前选中值:timer

vue
<script setup lang="ts">
import { ref } from "vue";
import { NavigationRail, NavigationRailItem } from "@sharpdotnut/dotsharp";
import alarm from "@iconify-icons/material-symbols/alarm";
import hourglass from "@iconify-icons/material-symbols/hourglass";
import schedule from "@iconify-icons/material-symbols/schedule";
import timer from "@iconify-icons/material-symbols/timer";

const current = ref("timer");
</script>

<template>
  <div class="flex h-[26rem] w-full overflow-hidden rounded-ds border border-ds-line bg-ds-bg">
    <NavigationRail v-model:value="current" :menu="false" label="时钟">
      <NavigationRailItem label="计时器" value="timer" :icon="hourglass" />
      <NavigationRailItem label="闹钟" value="alarm" :icon="alarm" />
      <NavigationRailItem label="时钟" value="clock" :icon="schedule" />
      <NavigationRailItem label="秒表" value="stopwatch" :icon="timer" />
    </NavigationRail>
    <div class="flex flex-1 items-start p-6 text-sm text-ds-muted">应用内容</div>
  </div>
</template>

需要菜单按钮时传入 menu-icon:不可展开时点击不改变宽度,只触发 menu 事件,由使用方决定打开什么 (例如一个 modal drawer)。

可展开导航栏 ​

加 expandable 后由菜单按钮切换折叠 / 展开:折叠 96px 只留图标与下方文字,展开到 expandedWidth (默认 256px,夹在 220–360px 之间)时文字移到图标右侧,选中指示变成整行胶囊。

presentation 决定展开时如何占用空间:

presentation展开时的布局
"standard"(默认)挤开同一行里的内容(relative + 宽度动画)
"modal"用 absolute 覆盖最近的定位容器,带遮罩、焦点陷阱与 Escape 关闭

modal 依赖父元素是定位容器(Tailwind 的 relative 类),遮罩只覆盖这个容器,不是视口级浮层。

挤开内容(standard) ​

应用内容

当前选中值:timer,展开:false

vue
<script setup lang="ts">
import { ref } from "vue";
import { NavigationRail, NavigationRailItem } from "@sharpdotnut/dotsharp";
import alarm from "@iconify-icons/material-symbols/alarm";
import hourglass from "@iconify-icons/material-symbols/hourglass";
import menu from "@iconify-icons/material-symbols/menu";
import menuOpen from "@iconify-icons/material-symbols/menu-open";
import schedule from "@iconify-icons/material-symbols/schedule";
import timer from "@iconify-icons/material-symbols/timer";

const current = ref("timer");
const expanded = ref(false);
</script>

<template>
  <div class="flex h-[26rem] w-full overflow-hidden rounded-ds border border-ds-line bg-ds-bg">
    <NavigationRail
      v-model:value="current"
      v-model:expanded="expanded"
      expandable
      :menu-icon="menu"
      :menu-expanded-icon="menuOpen"
      menu-label="展开或收起导航"
      label="时钟"
    >
      <NavigationRailItem label="计时器" value="timer" :icon="hourglass" />
      <NavigationRailItem label="闹钟" value="alarm" :icon="alarm" />
      <NavigationRailItem label="时钟" value="clock" :icon="schedule" />
      <NavigationRailItem label="秒表" value="stopwatch" :icon="timer" />
    </NavigationRail>
    <div class="flex flex-1 items-start p-6 text-sm text-ds-muted">应用内容</div>
  </div>
</template>

覆盖内容(modal) ​

presentation="modal" 时展开态覆盖在容器内容之上:带遮罩,点击遮罩、按 Escape 或点击菜单按钮都会收起; 导航本身带 role="dialog" 与 aria-modal="true",焦点在内部循环,收起后归还给原元素。

应用内容(展开时被 rail 覆盖)
vue
<script setup lang="ts">
import { ref } from "vue";
import { Button, NavigationRail, NavigationRailItem } from "@sharpdotnut/dotsharp";
import alarm from "@iconify-icons/material-symbols/alarm";
import hourglass from "@iconify-icons/material-symbols/hourglass";
import menu from "@iconify-icons/material-symbols/menu";
import menuOpen from "@iconify-icons/material-symbols/menu-open";
import schedule from "@iconify-icons/material-symbols/schedule";
import timer from "@iconify-icons/material-symbols/timer";

const current = ref("timer");
const expanded = ref(false);
</script>

<template>
  <div
    class="relative flex h-[24rem] w-full overflow-hidden rounded-ds border border-ds-line bg-ds-bg"
  >
    <NavigationRail
      v-model:value="current"
      v-model:expanded="expanded"
      expandable
      presentation="modal"
      :menu-icon="menu"
      :menu-expanded-icon="menuOpen"
      menu-label="收起导航"
      label="时钟"
    >
      <NavigationRailItem label="计时器" value="timer" :icon="hourglass" />
      <NavigationRailItem label="闹钟" value="alarm" :icon="alarm" />
      <NavigationRailItem label="时钟" value="clock" :icon="schedule" />
      <NavigationRailItem label="秒表" value="stopwatch" :icon="timer" />
    </NavigationRail>

    <div class="flex flex-1 flex-col items-start gap-3 p-6">
      <Button @click="expanded = true">展开导航</Button>
      <span class="text-sm text-ds-muted">应用内容(展开时被 rail 覆盖)</span>
    </div>
  </div>
</template>

展开宽度 ​

expandedWidth 接受数字(按 px 处理),超出 220–360 会被夹到区间端点:

应用内容
vue
<NavigationRail
  v-model:value="current"
  v-model:expanded="expanded"
  expandable
  :expanded-width="300"
  :menu-icon="menu"
  :menu-expanded-icon="menuOpen"
  label="时钟"
>
  <NavigationRailItem label="计时器" value="timer" :icon="hourglass" />
  <NavigationRailItem label="闹钟" value="alarm" :icon="alarm" />
  <NavigationRailItem label="时钟" value="clock" :icon="schedule" />
</NavigationRail>

内置菜单按钮 ​

行为不可展开(默认)expandable
点击效果触发 menu 事件,宽度不变切换 v-model:expanded
aria-expanded不输出输出当前展开状态
aria-controls不输出指向内部导航项列表的 id
图标切换始终用 menu-icon展开时优先用 menu-expanded-icon
  • menu(默认 true):是否渲染按钮;只有提供 menu-icon 或 menu 插槽时才真正出现。
  • menu-label:按钮的 aria-label。
  • menu 是作用域插槽,参数为 { expanded },可替换成任意图标或自定义按钮内容:
vue
<script setup lang="ts">
import { ref } from "vue";
import { Icon, NavigationRail, NavigationRailItem } from "@sharpdotnut/dotsharp";
import hourglass from "@iconify-icons/material-symbols/hourglass";
import menu from "@iconify-icons/material-symbols/menu";
import menuOpen from "@iconify-icons/material-symbols/menu-open";

const expanded = ref(false);
</script>

<template>
  <NavigationRail v-model:expanded="expanded" expandable>
    <template #menu="{ expanded }">
      <Icon :icon="expanded ? menuOpen : menu" size="24" />
    </template>
    <NavigationRailItem label="计时器" :icon="hourglass" />
  </NavigationRail>
</template>

链接、禁用与选中图标 ​

  • href:有值时根元素渲染成 <a href>,仍保留 aria-current="page",可以配合 @click.prevent 交给路由处理。
  • disabled:按钮带原生 disabled;链接带 aria-disabled="true" 与 tabindex="-1",并且不再改写选中值。
  • active-icon:选中时替换 icon,折叠与展开两种排版都会生效。
应用内容
vue
<script setup lang="ts">
import { NavigationRail, NavigationRailItem } from "@sharpdotnut/dotsharp";
import alarm from "@iconify-icons/material-symbols/alarm";
import alarmOn from "@iconify-icons/material-symbols/alarm-on";
import hourglass from "@iconify-icons/material-symbols/hourglass";
import timer from "@iconify-icons/material-symbols/timer";
</script>

<template>
  <NavigationRail :value="'alarm'" :menu="false" label="链接示例">
    <NavigationRailItem label="计时器" value="timer" :icon="hourglass" href="/timer" />
    <NavigationRailItem
      label="闹钟"
      value="alarm"
      :icon="alarm"
      :active-icon="alarmOn"
      href="/alarm"
    />
    <NavigationRailItem label="秒表" value="stopwatch" :icon="timer" disabled />
  </NavigationRail>
</template>

选中值与索引 ​

NavigationRailItem 省略 value 时用自己的数字索引(从 0 开始)作为标识;v-model:value 默认是 0, 所以首项默认选中。

应用内容

当前选中索引:0

vue
<script setup lang="ts">
import { ref } from "vue";
import { NavigationRail, NavigationRailItem } from "@sharpdotnut/dotsharp";
import alarm from "@iconify-icons/material-symbols/alarm";
import hourglass from "@iconify-icons/material-symbols/hourglass";
import schedule from "@iconify-icons/material-symbols/schedule";

const index = ref(0);
</script>

<template>
  <NavigationRail v-model:value="index" :menu="false" label="索引示例">
    <NavigationRailItem label="第一项" :icon="hourglass" />
    <NavigationRailItem label="第二项" :icon="alarm" />
    <NavigationRailItem label="第三项" :icon="schedule" />
  </NavigationRail>
</template>

索引由 NavigationRail 按最终渲染顺序注入,规则见父子组件以及插槽统一范式。

单项内容 ​

  • label:纯文本,始终显示——折叠时在图标下方,展开时在图标右侧;它同时是该项的无障碍名称。
  • icon:与 Icon 的 icon 同源,尺寸固定 24px。
  • 默认插槽:优先于 label,适合自定义排版;过长文字会截断为省略号。
vue
<NavigationRailItem label="计时器" :icon="hourglass" value="timer" />
<NavigationRailItem value="custom" :icon="schedule">
  <span class="font-medium">自定义内容</span>
</NavigationRailItem>

API ​

属性类型默认值说明
v-model:valuestring | number0当前选中值,对应子项的 value 或索引
v-model:expandedbooleanfalse仅 expandable:是否展开为 expandedWidth
expandablebooleanfalse允许菜单按钮展开 / 收起
presentation"standard" | "modal""standard"展开时挤开内容,或在定位容器内覆盖内容
expandedWidthnumber256展开宽度,夹在 220–360px 之间
menubooleantrue是否渲染内置菜单按钮
menu-labelstring"Toggle navigation"菜单按钮的 aria-label
menu-iconIconifyIcon | string—折叠态菜单图标;没有图标且没有 menu 插槽时按钮不渲染
menu-expanded-iconIconifyIcon | string—展开态菜单图标,省略时沿用 menu-icon
labelstring—无障碍名称,写入 <nav> 的 aria-label

事件:

事件触发时机
menu不可展开时点击内置菜单按钮;不改变 rail 宽度

插槽:

插槽作用域说明
default—一组 NavigationRailItem
menu{ expanded: boolean }替换内置菜单按钮的内容
属性类型默认值说明
labelstring—纯文本内容,始终显示;存在默认插槽时被插槽覆盖
iconIconifyIcon | string—图标,尺寸 24px,渲染在文字/插槽之前
activeIconIconifyIcon | string—选中时替换 icon
valuestring | number所在位置索引子项标识
hrefstring—有值时根元素渲染成 <a>,用于真实链接导航
disabledbooleanfalse禁用,按钮用原生 disabled,链接用 aria-disabled
  • 根元素是 button(href 存在时是 a),选中项带 aria-current="page"。
  • 默认插槽优先于 label;与 icon 同时使用时插槽内容排在图标之后。
  • 选中指示器:折叠态只包住图标(8×14 胶囊),展开态是整行 56px 胶囊。

可访问性 ​

  • 根元素是 <nav>;请通过 label 给出可读名称,内置菜单按钮用 menu-label。
  • 折叠态不再隐藏文字:每一项的可读名称就是它的 label(或插槽内容),无需额外的 aria-label。
  • expandable 的菜单按钮带 aria-expanded 与 aria-controls;不可展开的按钮没有这两个属性,只触发 menu 事件。
  • presentation="modal" 展开时 rail 带 role="dialog" 与 aria-modal="true":焦点在内部循环,Escape 收起, 收起后焦点归还给触发元素;遮罩点击也能收起。它不是视口级浮层,只覆盖最近的定位容器。
  • 选中项用 aria-current="page" 表达当前位置,不要只依赖颜色。