移动端导航轻提示

轻提示Toast

在屏幕中央或底部短暂提示一条结果信息(成功 / 失败 / 警告 / 加载中),不打断当前操作

反馈 规格 12 · 轻提示 Toast 6 端实现 触摸优先

引入(H5 原生;其余 5 端见「快速开始」)
<!-- ① 令牌:PC 令牌 + 移动端 --kole-m-* 合成单文件,引一次 -->
<link rel="stylesheet" href="kole-ui/mobile/tokens.css">

<!-- ② 本组件样式(全量则用 kole-ui/mobile/components/index.css) -->
<link rel="stylesheet" href="kole-ui/mobile/components/mobile-toast.css">

<!-- ③ 结构照抄下方任一演示块(类名与 6 端实现一致) -->

演示

每个演示都是真实渲染:预览帧加载 frameworks-mobile/Toast.html?demo=<id>(只显示该演示块),代码是该演示块在演示页里的原文,可复制。全部演示同屏可看 演示页 ↗。

01 组件类型

居中提示position=center

最常用的形态:居中显示一条结果,不打断当前操作。

查看代码(演示页原文 · 12 行)
frameworks-mobile/Toast.html · center
<section class="demo-block" data-demo="center">
  <p class="demo-label">居中提示(position=center,点按钮显示;再点一次收起)</p>
  <div class="demo-frame" data-assert="toast-center">
    <div class="demo-frame__body">
      <button class="demo-trigger" type="button" id="toast-center-trigger"
              data-behavior="click-toggles-class:#toast-center-box|is-open">显示居中提示</button>
    </div>
    <div class="kole-m-toast kole-m-toast--center" id="toast-center-box" role="status" aria-live="polite">
      <span class="kole-m-toast__text">已保存到草稿箱</span>
    </div>
  </div>
</section>
语义语气tone=success|warning

成功 / 警告等语气只改变图标与语义,文字始终是唯一的信息载体。

查看代码(演示页原文 · 13 行)
frameworks-mobile/Toast.html · tone
<section class="demo-block" data-demo="tone">
  <p class="demo-label">五种语气(tone=success / warning / danger:同一浮层,语义由文字承担)</p>
  <div class="demo-frame" data-assert="toast-tone">
    <div class="kole-m-toast kole-m-toast--center is-open" role="status" aria-live="polite">
      <span class="kole-m-toast__icon" aria-hidden="true">✓</span>
      <span class="kole-m-toast__text">提交成功</span>
    </div>
    <div class="kole-m-toast kole-m-toast--bottom is-open" role="status" aria-live="polite">
      <span class="kole-m-toast__icon" aria-hidden="true">!</span>
      <span class="kole-m-toast__text">网络不稳定,已切换离线模式</span>
    </div>
  </div>
</section>
顶部提示position=top

出现在顶部时不遮挡底部操作区,适合「已复制」这类轻反馈。

查看代码(演示页原文 · 8 行)
frameworks-mobile/Toast.html · top
<section class="demo-block" data-demo="top">
  <p class="demo-label">顶部提示(position=top:不遮挡底部操作区)</p>
  <div class="demo-frame" data-assert="toast-top">
    <div class="kole-m-toast kole-m-toast--top is-open" role="status" aria-live="polite">
      <span class="kole-m-toast__text">已复制订单号</span>
    </div>
  </div>
</section>

02 组件状态

加载中状态 loading

tone=loading 时图标持续旋转并标记 aria-busy;减少动态偏好下停止旋转。

查看代码(演示页原文 · 9 行)
frameworks-mobile/Toast.html · loading
<section class="demo-block" data-demo="loading">
  <p class="demo-label">加载中(tone=loading:图标旋转,aria-busy=true;reduced-motion 下停止)</p>
  <div class="demo-frame" data-assert="toast-loading">
    <div class="kole-m-toast kole-m-toast--center kole-m-toast--loading is-open" role="status" aria-live="polite" aria-busy="true">
      <span class="kole-m-toast__icon" aria-hidden="true">◌</span>
      <span class="kole-m-toast__text">正在提交…</span>
    </div>
  </div>
</section>
带遮罩mask=true

mask=true 时遮罩吸收手势,下方内容在提示期间不可点。

查看代码(演示页原文 · 9 行)
frameworks-mobile/Toast.html · mask
<section class="demo-block" data-demo="mask">
  <p class="demo-label">带遮罩(mask=true:吸收手势,下方不可点;默认展开以便对照)</p>
  <div class="demo-frame" data-assert="toast-mask">
    <div class="kole-m-toast__mask is-open" aria-hidden="true"></div>
    <div class="kole-m-toast kole-m-toast--center is-open" role="status" aria-live="polite">
      <span class="kole-m-toast__text">请稍候,正在校验信息</span>
    </div>
  </div>
</section>

API

props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs

Props

名称类型默认值说明必传
tone'info' | 'success' | 'warning' | 'danger' | 'loading''info'变体 tone:语义配色与图标形态(规格 §12.3)N
position'center' | 'bottom' | 'top''center'变体 position:提示出现的位置(规格 §12.3)N
openbooleanfalse状态 open:展开可见(规格 §12.4)N
maskbooleanfalse是否显示遮罩并阻断下方交互(规格 §12.3)N
textstring''提示文字(规格 §12.2 text)N

「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。

事件

名称参数说明

插槽

名称说明
default提示内容(与 text 二选一;text 优先)

CSS 变量

组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。

名称默认值说明
--kole-m-toast-radiusvar(--kole-radius-large)组件内部默认值,可在业务侧覆盖
--kole-m-toast-max-width70%组件内部默认值,可在业务侧覆盖

何时使用

  • 在屏幕中央或底部短暂提示一条结果信息(成功 / 失败 / 警告 / 加载中),不打断当前操作
  • 提示本身不可点(不抢焦点、不阻断),mask=true 时遮罩吸收手势
  • 出现 / 消失动画 240ms;自动关闭时长由宿主控制(本组件只负责显示态)
  • 容器 role="status" + aria-live="polite"(结果朗读一次,不反复打断)
  • 图标为装饰(aria-hidden="true"),语义全部由文字承担

交互与触控

  • 提示本身不可点(不抢焦点、不阻断),mask=true 时遮罩吸收手势
  • 出现 / 消失动画 240ms;自动关闭时长由宿主控制(本组件只负责显示态)

无障碍

  • 容器 role="status" + aria-live="polite"(结果朗读一次,不反复打断)
  • 图标为装饰(aria-hidden="true"),语义全部由文字承担

从「该用哪一个」的角度区分;PC 端的对应实现见 PC 文档站。

组件何时用它而不是本组件
弹出层Popup需要承载内容与操作时用弹出层,轻提示只承载一句结果
通知栏NoticeBar常驻的通告用通知栏,一次性反馈用轻提示
对话框Dialog需要用户做决定(确认 / 取消)时用对话框,提示不需要决定

规格未定 / 禁止发明

类别条目
禁止发明自动关闭的默认时长
禁止发明多条提示的排队 / 合并策略
规格未定单行文字的最大宽度与换行规则
规格未定是否需要点击穿透设置

结构(anatomy)

字段说明
toast浮层本体,居中于视口
icon状态图标(可选)
text提示文字
mask可选透明遮罩(mask=true 时阻断下方点击)

变体维度与类名映射

类名映射由构建脚本从契约 variantClasses 生成,并被 verify:mobile-docs 逐条对照组件 CSS 校验(类/变量必须真实存在)。

维度取值对应类名 / 变量
toneinfo / success / warning / danger / loading
info (由数据驱动,无专属类)
success (由数据驱动,无专属类)
warning (由数据驱动,无专属类)
danger (由数据驱动,无专属类)
loading .kole-m-toast--loading
positioncenter / bottom / top
center .kole-m-toast--center
bottom .kole-m-toast--bottom
top .kole-m-toast--top
maskfalse / true
false (由数据驱动,无专属类)
true .kole-m-toast__mask

代表变体

变体标签
tone=info · position=center · mask=false居中提示
tone=success · position=bottom · mask=false底部成功提示
tone=loading · position=center · mask=true加载中(带遮罩)

用到的令牌

构建时从本组件样式表扫描得出。蓝色为移动端自有令牌,绿色为继承的 PC 令牌(改一处两端生效)。

--kole-m-duration-slide --kole-m-ease-slide --kole-m-font-size-label --kole-m-font-size-title --kole-m-navbar-height --kole-m-safe-bottom --kole-m-safe-top --kole-m-touch-target --kole-color-mask-strong --kole-color-text-inverse --kole-color-tooltip-bg --kole-font-family --kole-radius-large --kole-space-16 --kole-space-20 --kole-space-8 --kole-m-toast-max-width --kole-m-toast-radius

6 端源码

同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。

frameworks-mobile/Toast.css · 纯样式(CSS) · 91 行
frameworks-mobile/Toast.css
/* Kole UI Mobile · Toast 样式 — 对齐移动端规格 §12
   轻提示:深底 + 反色文字(亮色 15.78:1 / 暗色 15.00:1,脚本实测),居中于视口;
   不抢焦点、不阻断交互(mask=true 时遮罩吸收手势);动效 240ms。 */

.kole-m-toast {
  --kole-m-toast-radius: var(--kole-radius-large);
  --kole-m-toast-max-width: 70%;
  position: fixed;
  z-index: 2100;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--kole-space-8);
  max-width: var(--kole-m-toast-max-width);
  padding: var(--kole-space-16) var(--kole-space-20);
  border-radius: var(--kole-m-toast-radius);
  background: var(--kole-color-tooltip-bg);
  color: var(--kole-color-text-inverse);
  font-family: var(--kole-font-family);
  font-size: var(--kole-m-font-size-label);
  line-height: 1.4;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--kole-m-duration-slide) var(--kole-m-ease-slide);
}

.kole-m-toast.is-open {
  opacity: 1;
}

/* 变体 position:三个位置(默认居中) */
.kole-m-toast--center {
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.kole-m-toast--bottom {
  left: 50%;
  bottom: calc(var(--kole-m-touch-target) + var(--kole-m-safe-bottom));
  transform: translateX(-50%);
}

.kole-m-toast--top {
  left: 50%;
  top: calc(var(--kole-m-navbar-height) + var(--kole-m-safe-top));
  transform: translateX(-50%);
}

.kole-m-toast__icon {
  flex: 0 0 auto;
  font-size: var(--kole-m-font-size-title);
  line-height: 1;
}

.kole-m-toast__text {
  word-break: break-word;
}

/* 状态 loading:图标持续旋转(reduced-motion 下停止) */
.kole-m-toast--loading .kole-m-toast__icon {
  animation: kole-m-toast-spin 1s linear infinite;
}

@keyframes kole-m-toast-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .kole-m-toast { transition: none; }
  .kole-m-toast--loading .kole-m-toast__icon { animation: none; }
}

/* 变体 mask=true:透明遮罩吸收手势,下方不可点 */
.kole-m-toast__mask {
  position: fixed;
  inset: 0;
  z-index: 2099;
  background: var(--kole-color-mask-strong);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--kole-m-duration-slide) var(--kole-m-ease-slide);
}

.kole-m-toast__mask.is-open {
  opacity: 1;
  pointer-events: auto;
}
frameworks-mobile/Toast.html · H5 原生(无框架) · 119 行
frameworks-mobile/Toast.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Kole UI Mobile · Toast(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="Toast.css">
<style>
  body { margin: 0; background: var(--kole-color-page-bg); font-family: var(--kole-font-family); color: var(--kole-color-text-body); }
  .demo { max-width: 375px; margin: 0 auto; padding: var(--kole-m-gutter) 0; }
  .demo-label { margin: 0; padding: var(--kole-space-12) var(--kole-m-gutter) var(--kole-space-8);
    font-size: var(--kole-m-font-size-label); color: var(--kole-color-text-secondary); }
  /* 展示框:transform 建立包含块,提示与遮罩限定在框内(生产环境落在视口上) */
  .demo-frame { position: relative; max-width: 375px; margin: 0 auto; height: 200px;
    overflow: hidden; transform: translateZ(0); background: var(--kole-color-page-bg);
    border-block: 1px solid var(--kole-color-border); }
  .demo-frame__body { padding: var(--kole-m-gutter); font-size: var(--kole-m-font-size-label);
    color: var(--kole-color-text-secondary); line-height: 1.7; }
  .demo-trigger { min-height: var(--kole-m-touch-target); padding: 0 var(--kole-m-gutter);
    border: 1px solid var(--kole-color-border); border-radius: var(--kole-radius-base);
    background: var(--kole-color-card-bg); color: var(--kole-color-text-body);
    font-family: inherit; font-size: var(--kole-m-font-size-label); cursor: pointer; touch-action: manipulation; }
  .demo-trigger:focus-visible { outline: 2px solid var(--kole-color-focus-ring); outline-offset: 2px; }
  .demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="center">
    <p class="demo-label">居中提示(position=center,点按钮显示;再点一次收起)</p>
    <div class="demo-frame" data-assert="toast-center">
      <div class="demo-frame__body">
        <button class="demo-trigger" type="button" id="toast-center-trigger"
                data-behavior="click-toggles-class:#toast-center-box|is-open">显示居中提示</button>
      </div>
      <div class="kole-m-toast kole-m-toast--center" id="toast-center-box" role="status" aria-live="polite">
        <span class="kole-m-toast__text">已保存到草稿箱</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="tone">
    <p class="demo-label">五种语气(tone=success / warning / danger:同一浮层,语义由文字承担)</p>
    <div class="demo-frame" data-assert="toast-tone">
      <div class="kole-m-toast kole-m-toast--center is-open" role="status" aria-live="polite">
        <span class="kole-m-toast__icon" aria-hidden="true">✓</span>
        <span class="kole-m-toast__text">提交成功</span>
      </div>
      <div class="kole-m-toast kole-m-toast--bottom is-open" role="status" aria-live="polite">
        <span class="kole-m-toast__icon" aria-hidden="true">!</span>
        <span class="kole-m-toast__text">网络不稳定,已切换离线模式</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="top">
    <p class="demo-label">顶部提示(position=top:不遮挡底部操作区)</p>
    <div class="demo-frame" data-assert="toast-top">
      <div class="kole-m-toast kole-m-toast--top is-open" role="status" aria-live="polite">
        <span class="kole-m-toast__text">已复制订单号</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="loading">
    <p class="demo-label">加载中(tone=loading:图标旋转,aria-busy=true;reduced-motion 下停止)</p>
    <div class="demo-frame" data-assert="toast-loading">
      <div class="kole-m-toast kole-m-toast--center kole-m-toast--loading is-open" role="status" aria-live="polite" aria-busy="true">
        <span class="kole-m-toast__icon" aria-hidden="true">◌</span>
        <span class="kole-m-toast__text">正在提交…</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="mask">
    <p class="demo-label">带遮罩(mask=true:吸收手势,下方不可点;默认展开以便对照)</p>
    <div class="demo-frame" data-assert="toast-mask">
      <div class="kole-m-toast__mask is-open" aria-hidden="true"></div>
      <div class="kole-m-toast kole-m-toast--center is-open" role="status" aria-live="polite">
        <span class="kole-m-toast__text">请稍候,正在校验信息</span>
      </div>
    </div>
  </section>
</div>
<script>
  /* 演示页交互:点触发器切换提示显示态(真实业务里由请求结果驱动)。 */
  (function () {
    var trig = document.getElementById('toast-center-trigger');
    var box = document.getElementById('toast-center-box');
    if (!trig || !box) return;
    trig.addEventListener('click', function () {
      box.classList.toggle('is-open');
    });
  })();
</script>
<script>
  /* ?demo=<id> → 只显示该演示块(文档站按块预览用;无参数时全部显示,测试与回归走无参数路径) */
  (function () {
    var id = new URLSearchParams(location.search).get('demo');
    if (!id) return;
    var blocks = Array.prototype.slice.call(document.querySelectorAll('.demo-block'));
    var hit = false;
    blocks.forEach(function (b) {
      var on = b.getAttribute('data-demo') === id;
      if (on) hit = true;
      b.hidden = !on;
    });
    if (!hit) { blocks.forEach(function (b) { b.hidden = false; }); return; }
    document.body.classList.add('demo-single');
    blocks.forEach(function (b) {
      var label = b.querySelector('.demo-label');
      if (label && !b.hidden) label.hidden = true;
    });
  })();
</script>
</body>
</html>
frameworks-mobile/Toast.jsx · React · 35 行
frameworks-mobile/Toast.jsx
import React from 'react';
import './Toast.css';

/* 轻提示(移动端)— 规格 §12;容器 role=status + aria-live=polite(朗读一次不打断);
   提示本身不可点、不抢焦点;mask=true 时遮罩吸收手势。 */
export default function Toast({
  tone = 'info',
  position = 'center',
  open = false,
  mask = false,
  text = '',
  children = null,
}) {
  const content = text || children;
  const cls =
    'kole-m-toast' +
    ` kole-m-toast--${position}` +
    (tone === 'loading' ? ' kole-m-toast--loading' : '') +
    (open ? ' is-open' : '');

  return (
    <>
      <div className={'kole-m-toast__mask' + (open && mask ? ' is-open' : '')} aria-hidden="true" />
      <div className={cls} role="status" aria-live="polite" aria-busy={tone === 'loading' ? 'true' : undefined}>
        {tone === 'loading' ? (
          <span className="kole-m-toast__icon" aria-hidden="true">
            ◌
          </span>
        ) : null}
        <span className="kole-m-toast__text">{content}</span>
      </div>
    </>
  );
}
frameworks-mobile/Toast.vue2.vue · Vue 2 · 49 行
frameworks-mobile/Toast.vue2.vue
<template>
  <div>
    <div class="kole-m-toast__mask" :class="{ 'is-open': open && mask }" aria-hidden="true"></div>
    <div
      class="kole-m-toast"
      :class="toastClass"
      role="status"
      aria-live="polite"
      :aria-busy="tone === 'loading' ? 'true' : null"
    >
      <span v-if="tone === 'loading'" class="kole-m-toast__icon" aria-hidden="true">◌</span>
      <span class="kole-m-toast__text">{{ content }}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'KoleMToast',
  props: {
    tone: { type: String, default: 'info' },
    position: { type: String, default: 'center' },
    open: { type: Boolean, default: false },
    mask: { type: Boolean, default: false },
    text: { type: String, default: '' }
  },
  computed: {
    content: function () {
      if (this.text) return this.text;
      var nodes = (this.$slots && this.$slots.default) || [];
      return nodes
        .map(function (n) {
          return n.children && typeof n.children === 'string' ? n.children : '';
        })
        .join('');
    },
    toastClass: function () {
      return [
        'kole-m-toast--' + this.position,
        this.tone === 'loading' ? 'kole-m-toast--loading' : '',
        this.open ? 'is-open' : ''
      ].filter(Boolean);
    }
  }
};
</script>

<style src="./Toast.css"></style>
frameworks-mobile/Toast.vue3.vue · Vue 3 · 43 行
frameworks-mobile/Toast.vue3.vue
<template>
  <div>
    <div class="kole-m-toast__mask" :class="{ 'is-open': open && mask }" aria-hidden="true"></div>
    <div
      class="kole-m-toast"
      :class="toastClass"
      role="status"
      aria-live="polite"
      :aria-busy="tone === 'loading' ? 'true' : null"
    >
      <span v-if="tone === 'loading'" class="kole-m-toast__icon" aria-hidden="true">◌</span>
      <span class="kole-m-toast__text">{{ content }}</span>
    </div>
  </div>
</template>

<script setup>
import { computed, useSlots } from 'vue';

const props = defineProps({
  tone: { type: String, default: 'info' },
  position: { type: String, default: 'center' },
  open: { type: Boolean, default: false },
  mask: { type: Boolean, default: false },
  text: { type: String, default: '' }
});

const slots = useSlots();
const content = computed(() => {
  if (props.text) return props.text;
  const nodes = slots.default ? slots.default() : [];
  return nodes.map((n) => (typeof n.children === 'string' ? n.children : '')).join('');
});

const toastClass = computed(() => [
  `kole-m-toast--${props.position}`,
  props.tone === 'loading' ? 'kole-m-toast--loading' : '',
  props.open ? 'is-open' : ''
].filter(Boolean));
</script>

<style src="./Toast.css"></style>
frameworks-mobile/Toast.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 105 行
frameworks-mobile/Toast.uniapp.vue
<template>
  <view>
    <view class="kole-m-toast__mask" :class="{ 'is-open': open && mask }" aria-hidden="true"></view>
    <view
      class="kole-m-toast"
      :class="toastClass"
      :role="'status'"
      :aria-busy="tone === 'loading' ? 'true' : 'false'"
    >
      <text v-if="tone === 'loading'" class="kole-m-toast__icon">◌</text>
      <text class="kole-m-toast__text">{{ content }}</text>
    </view>
  </view>
</template>

<script setup>
/* uni-app 端 · 轻提示(移动端)— 规格 §12
   跨端差异:用 view / text;尺寸用 rpx(2rpx ≈ 1px);朗读靠 role="status"(H5 产物生效);
   提示不承载交互,因此没有 touch 事件。 */
import { computed, useSlots } from 'vue';

const props = defineProps({
  tone: { type: String, default: 'info' },
  position: { type: String, default: 'center' },
  open: { type: Boolean, default: false },
  mask: { type: Boolean, default: false },
  text: { type: String, default: '' }
});

const slots = useSlots();
const content = computed(() => {
  if (props.text) return props.text;
  const nodes = slots.default ? slots.default() : [];
  return nodes.map((n) => (typeof n.children === 'string' ? n.children : '')).join('');
});

const toastClass = computed(() => [
  `kole-m-toast--${props.position}`,
  props.tone === 'loading' ? 'kole-m-toast--loading' : '',
  props.open ? 'is-open' : ''
].filter(Boolean));
</script>

<style>
.kole-m-toast {
  --kole-m-toast-max-width: 70%;
  --kole-m-touch-target: 88rpx;
  --kole-m-navbar-height: 88rpx;
  --kole-m-font-size-label: 28rpx;
  --kole-m-font-size-title: 34rpx;
  position: fixed;
  z-index: 2100;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: var(--kole-m-toast-max-width);
  padding: 32rpx 40rpx;
  border-radius: 16rpx;
  background-color: var(--kole-color-tooltip-bg);
  color: var(--kole-color-text-inverse);
  font-size: var(--kole-m-font-size-label);
  text-align: center;
  opacity: 0;
}

.kole-m-toast.is-open { opacity: 1; }

.kole-m-toast--center {
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.kole-m-toast--bottom {
  left: 50%;
  bottom: calc(var(--kole-m-touch-target) + var(--kole-m-safe-bottom));
  transform: translateX(-50%);
}

.kole-m-toast--top {
  left: 50%;
  top: calc(var(--kole-m-navbar-height) + var(--kole-m-safe-top));
  transform: translateX(-50%);
}

.kole-m-toast__icon {
  margin-bottom: 16rpx;
  font-size: var(--kole-m-font-size-title);
}

.kole-m-toast__text { word-break: break-word; }

.kole-m-toast__mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2099;
  background-color: var(--kole-color-mask-strong);
  opacity: 0;
}
</style>

测试与回归

断言在真实的 375×640 设备帧里跑(引擎与 PC 侧共用 tests/_runtime.js,触控行为动词来自移动端 tests/mobile/_behaviors.js)。

断言 16 条 · 全部通过 报告 2026-09-22 23:01:05

复现命令
node site/dev-server.js &
REG_BASE=http://127.0.0.1:3311 node tools/run-mobile-regression.mjs   # 全量 5 个组件
npm run verify:mobile-docs                                           # 本页内容完整性 + API 与源码一致性

设计契约

components/mobile-toast.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "confidence": "high",
  "specSection": "12 · 轻提示 Toast",
  "slug": "mobile-toast",
  "name": "轻提示 Toast",
  "semanticTypeCandidates": [
    "toast",
    "message",
    "snackbar"
  ],
  "variantDimensions": [
    {
      "name": "tone",
      "values": [
        "info",
        "success",
        "warning",
        "danger",
        "loading"
      ]
    },
    {
      "name": "position",
      "values": [
        "center",
        "bottom",
        "top"
      ]
    },
    {
      "name": "mask",
      "values": [
        "false",
        "true"
      ]
    }
  ],
  "representativeVariants": [
    {
      "tone": "info",
      "position": "center",
      "mask": "false",
      "label": "居中提示"
    },
    {
      "tone": "success",
      "position": "bottom",
      "mask": "false",
      "label": "底部成功提示"
    },
    {
      "tone": "loading",
      "position": "center",
      "mask": "true",
      "label": "加载中(带遮罩)"
    }
  ],
  "anatomy": {
    "toast": "浮层本体,居中于视口",
    "icon": "状态图标(可选)",
    "text": "提示文字",
    "mask": "可选透明遮罩(mask=true 时阻断下方点击)"
  },
  "structurePatterns": {
    "tone": "info / success / warning / danger / loading",
    "position": "center / bottom / top",
    "mask": "false / true(阻断交互)"
  },
  "usageHints": [
    "在屏幕中央或底部短暂提示一条结果信息(成功 / 失败 / 警告 / 加载中),不打断当前操作",
    "提示本身不可点(不抢焦点、不阻断),mask=true 时遮罩吸收手势",
    "出现 / 消失动画 240ms;自动关闭时长由宿主控制(本组件只负责显示态)",
    "容器 role=\"status\" + aria-live=\"polite\"(结果朗读一次,不反复打断)",
    "图标为装饰(aria-hidden=\"true\"),语义全部由文字承担"
  ],
  "doNotInvent": [
    "自动关闭的默认时长",
    "多条提示的排队 / 合并策略"
  ],
  "unknowns": [
    "单行文字的最大宽度与换行规则",
    "是否需要点击穿透设置"
  ],
  "interaction": [
    "提示本身不可点(不抢焦点、不阻断),mask=true 时遮罩吸收手势",
    "出现 / 消失动画 240ms;自动关闭时长由宿主控制(本组件只负责显示态)"
  ],
  "accessibility": [
    "容器 role=\"status\" + aria-live=\"polite\"(结果朗读一次,不反复打断)",
    "图标为装饰(aria-hidden=\"true\"),语义全部由文字承担"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "tone",
        "type": "'info' | 'success' | 'warning' | 'danger' | 'loading'",
        "default": "'info'",
        "desc": "变体 tone:语义配色与图标形态(规格 §12.3)",
        "required": false
      },
      {
        "name": "position",
        "type": "'center' | 'bottom' | 'top'",
        "default": "'center'",
        "desc": "变体 position:提示出现的位置(规格 §12.3)",
        "required": false
      },
      {
        "name": "open",
        "type": "boolean",
        "default": "false",
        "desc": "状态 open:展开可见(规格 §12.4)",
        "required": false
      },
      {
        "name": "mask",
        "type": "boolean",
        "default": "false",
        "desc": "是否显示遮罩并阻断下方交互(规格 §12.3)",
        "required": false
      },
      {
        "name": "text",
        "type": "string",
        "default": "''",
        "desc": "提示文字(规格 §12.2 text)",
        "required": false
      }
    ],
    "events": [],
    "slots": [
      {
        "name": "default",
        "desc": "提示内容(与 text 二选一;text 优先)"
      }
    ]
  },
  "variantClasses": {
    "tone": {
      "info": [],
      "success": [],
      "warning": [],
      "danger": [],
      "loading": [
        ".kole-m-toast--loading"
      ]
    },
    "position": {
      "center": [
        ".kole-m-toast--center"
      ],
      "bottom": [
        ".kole-m-toast--bottom"
      ],
      "top": [
        ".kole-m-toast--top"
      ]
    },
    "mask": {
      "false": [],
      "true": [
        ".kole-m-toast__mask"
      ]
    }
  },
  "demos": [
    {
      "id": "center",
      "group": "01 组件类型",
      "title": "居中提示",
      "desc": "最常用的形态:居中显示一条结果,不打断当前操作。",
      "variant": "position=center"
    },
    {
      "id": "tone",
      "group": "01 组件类型",
      "title": "语义语气",
      "desc": "成功 / 警告等语气只改变图标与语义,文字始终是唯一的信息载体。",
      "variant": "tone=success|warning"
    },
    {
      "id": "top",
      "group": "01 组件类型",
      "title": "顶部提示",
      "desc": "出现在顶部时不遮挡底部操作区,适合「已复制」这类轻反馈。",
      "variant": "position=top"
    },
    {
      "id": "loading",
      "group": "02 组件状态",
      "title": "加载中",
      "desc": "tone=loading 时图标持续旋转并标记 aria-busy;减少动态偏好下停止旋转。",
      "variant": "状态 loading"
    },
    {
      "id": "mask",
      "group": "02 组件状态",
      "title": "带遮罩",
      "desc": "mask=true 时遮罩吸收手势,下方内容在提示期间不可点。",
      "variant": "mask=true"
    }
  ],
  "related": [
    {
      "slug": "mobile-popup",
      "why": "需要承载内容与操作时用弹出层,轻提示只承载一句结果"
    },
    {
      "slug": "mobile-noticebar",
      "why": "常驻的通告用通知栏,一次性反馈用轻提示"
    },
    {
      "slug": "mobile-dialog",
      "why": "需要用户做决定(确认 / 取消)时用对话框,提示不需要决定"
    }
  ]
}