链接Link
一段内联文字承载「跳转 / 打开下一级」或「触发一次轻量动作」
通用 规格 21 · 链接 Link 6 端实现 触摸优先
<!-- ① 令牌: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-link.css">
<!-- ③ 结构照抄下方任一演示块(类名与 6 端实现一致) -->
演示
每个演示都是真实渲染:预览帧加载 frameworks-mobile/Link.html?demo=<id>(只显示该演示块),代码是该演示块在演示页里的原文,可复制。全部演示同屏可看 演示页 ↗。
01 组件类型
默认品牌色;secondary 场景用 default 继承父级文字色;破坏性操作用 danger。
查看代码(演示页原文 · 9 行)
<section class="demo-block" data-demo="tone">
<p class="demo-label">语义色(tone=brand 默认 / default 继承父级 / danger 破坏性 / success)</p>
<div class="demo-box" data-assert="link-tone">
<a class="kole-m-link" href="#link-tone">查看订单详情</a>
<a class="kole-m-link kole-m-link--default" href="#link-tone">稍后再说</a>
<a class="kole-m-link kole-m-link--danger" href="#link-tone">删除记录</a>
<a class="kole-m-link kole-m-link--success" href="#link-tone">已完成认证</a>
</div>
</section>段落里的链接必须带下划线 —— 只靠颜色区分会让色觉障碍用户看不出来。
查看代码(演示页原文 · 11 行)
<section class="demo-block" data-demo="underline">
<p class="demo-label">下划线(underline=true:正文段落内必须能一眼分辨出链接)</p>
<div class="demo-box" data-assert="link-underline">
<p class="demo-para">
下单即表示同意
<a class="kole-m-link kole-m-link--underline" href="#link-underline">《用户服务协议》</a>
与
<a class="kole-m-link kole-m-link--underline" href="#link-underline">《隐私政策》</a>。
</p>
</div>
</section>02 组件状态
置灰且不响应;同时去掉 href 并移出 tab 序列,否则读屏仍会把它当可点项。
查看代码(演示页原文 · 6 行)
<section class="demo-block" data-demo="disabled">
<p class="demo-label">禁用(disabled:置灰且不响应;同时不渲染 href,否则读屏仍会把它当可点项)</p>
<div class="demo-box" data-assert="link-disabled">
<button class="kole-m-link kole-m-link--default is-disabled" type="button" disabled aria-disabled="true">退款申请(已超过 7 天)</button>
</div>
</section>列表里「查看全部」这类入口用 block,整行都可点(热区 44px),不必精准点文字。
查看代码(演示页原文 · 6 行)
<section class="demo-block" data-demo="block">
<p class="demo-label">整行链接(block=true:撑满容器,热区 44px,列表里整行可点)</p>
<div class="demo-box" data-assert="link-block">
<a class="kole-m-link is-block" href="#link-block">查看全部 12 条评价</a>
</div>
</section>无 href 时渲染 button:只回传事件,用于打开弹窗或展开区块,不做页面跳转。
查看代码(演示页原文 · 7 行)
<section class="demo-block" data-demo="event">
<p class="demo-label">不跳转的链接(无 href 时渲染 button:只回传事件,用于弹窗 / 展开)</p>
<div class="demo-box" data-assert="link-event">
<button class="kole-m-link" type="button" id="link-event-btn"
data-behavior="click-sets-attr:#link-event-btn|data-clicked|true">查看物流</button>
</div>
</section>尾部图标继承链接颜色,与文字间距 4px;图标自身不是热区。
查看代码(演示页原文 · 9 行)
<section class="demo-block" data-demo="with-icon">
<p class="demo-label">带图标(图标继承链接颜色,与文字间距 4px;图标自身不是热区)</p>
<div class="demo-box" data-assert="link-with-icon">
<a class="kole-m-link" href="#link-with-icon">
<span class="kole-m-link__label">去填写地址</span>
<span class="demo-arrow" aria-hidden="true">›</span>
</a>
</div>
</section>API
props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs
Props
| 名称 | 类型 | 默认值 | 说明 | 必传 |
|---|---|---|---|---|
tone | 'brand' | 'default' | 'danger' | 'success' | 'brand' | 变体 tone:brand 默认 / default 继承父级文字色(规格 §21.3) | N |
underline | boolean | false | 变体 underline:常驻下划线,正文段落内必须开启(规格 §21.3/§21.6) | N |
disabled | boolean | false | 置灰且不响应点击,并移出 tab 序列(规格 §21.4/§21.6) | N |
href | string | '' | 跳转地址;有值时渲染原生 a,禁用时不渲染该属性(规格 §21.2) | N |
text | string | '' | 纯文字快捷入口,与默认插槽二选一(规格 §21.2) | N |
block | boolean | false | 变体 block:撑满容器、整行可点(规格 §21.3) | N |
「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。
事件
| 名称 | 参数 | 说明 |
|---|---|---|
click | — | 点击时触发(disabled 时不触发;有 href 时导航由浏览器完成) |
插槽
| 名称 | 说明 |
|---|---|
default | 链接文字,可含图标(规格 §21.2 label) |
CSS 变量
组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。
| 名称 | 默认值 | 说明 |
|---|---|---|
| 本组件没有组件级 CSS 变量 | ||
何时使用
- 一段内联文字承载「跳转 / 打开下一级」或「触发一次轻量动作」
- 点击热区高度不小于 44px,文字可短但热区不缩水(内边距撑开)
- 按下反馈为颜色变化,不做位移缩放(避免长按抖动)
- 相邻链接之间至少留 8px 间距,防止误触(同时给出 16px 的更稳选择)
- 禁用链接点击不触发事件,也不跳转
交互与触控
- 点击热区高度不小于 44px,文字可短但热区不缩水(内边距撑开)
- 按下反馈为颜色变化,不做位移缩放(避免长按抖动)
- 相邻链接之间至少留 8px 间距,防止误触(同时给出 16px 的更稳选择)
- 禁用链接点击不触发事件,也不跳转
无障碍
- 有 href 用原生 a(可聚焦、可长按复制、读屏报「链接」);无 href 用原生 button
- 禁用链接不能用 href,且要 aria-disabled="true" + tabindex="-1"(移出 tab 序列)
- 链接文字必须自解释,不要出现孤立的「点击这里」(读屏会脱离上下文朗读)
- 颜色不是唯一线索:正文段落内的链接必须带下划线或图标,避免色觉障碍用户无法识别
相似组件
从「该用哪一个」的角度区分;PC 端的对应实现见 PC 文档站。
| 组件 | 何时用它而不是本组件 |
|---|---|
| 按钮Button | 触发一个明确动作(提交、确认)用按钮;链接只用于跳转或极轻的次要动作 |
| 单元格Cell | 整行进入下级、且行内有多段信息时用单元格;行内文字短语才用链接 |
| 图标Icon | 需要给链接配图标时用它渲染字形;图标不承担点击 |
规格未定 / 禁止发明
| 类别 | 条目 |
|---|---|
| 禁止发明 | 外跳协议处理(tel: / mailto: / 唤起 App) |
| 禁止发明 | 已访问状态的样式 |
| 禁止发明 | 链接的埋点 / 统计 |
| 规格未定 | 一屏内链接的最大推荐数量 |
| 规格未定 | 链接与相邻文字的推荐最小间距(同时给出 8px 与 16px 两档) |
结构(anatomy)
| 字段 | 说明 |
|---|---|
link | 根元素,有 href 时是原生 a,无 href 时是原生 button(只回传事件) |
label | 链接文字,单行不换行(超长由宿主截断) |
icon | 可选尾部图标,继承链接颜色,与文字间距 4px |
href | 跳转地址;禁用时不渲染该属性(否则仍可被打开) |
text | 纯文字快捷入口,与默认插槽二选一 |
变体维度与类名映射
类名映射由构建脚本从契约 variantClasses 生成,并被 verify:mobile-docs 逐条对照组件 CSS 校验(类/变量必须真实存在)。
| 维度 | 取值 | 对应类名 / 变量 |
|---|---|---|
tone | brand / default / danger / success | brand (由数据驱动,无专属类) default .kole-m-link--default danger .kole-m-link--danger success .kole-m-link--success |
underline | false / true | false (由数据驱动,无专属类) true .kole-m-link--underline |
block | false / true | false (由数据驱动,无专属类) true .is-block |
代表变体
| 变体 | 标签 |
|---|---|
tone=brand · underline=false | 品牌色链接(默认,行内主入口) |
tone=default · underline=false | 次要链接(继承父级文字色) |
tone=danger · underline=false | 破坏性操作链接 |
tone=brand · underline=true | 正文段落内链接(必须带下划线) |
用到的令牌
构建时从本组件样式表扫描得出。蓝色为移动端自有令牌,绿色为继承的 PC 令牌(改一处两端生效)。
6 端源码
同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。
frameworks-mobile/Link.css · 纯样式(CSS) · 61 行
/* Kole UI Mobile · Link 样式 — 对齐 .design_library/kole-ui-mobile/spec/移动端规格.md §21
文字链接:默认**不下划线**(移动端正文里满屏下划线噪声大),靠颜色区分;
需要「明确是链接」的场景(正文段落内、条款页)再加下划线。
与桌面端的差别:没有 hover,按下反馈用 :active 变色,不做透明度位移。 */
.kole-m-link {
display: inline-flex;
align-items: center;
gap: var(--kole-space-4);
box-sizing: border-box;
min-height: var(--kole-m-touch-target);
padding: 0 var(--kole-space-4);
border: 0;
border-radius: var(--kole-radius-small);
background: transparent;
color: var(--kole-color-brand);
font-family: inherit;
font-size: var(--kole-m-font-size-body);
line-height: 1.4;
text-align: start;
text-decoration: none;
cursor: pointer;
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
transition: color var(--kole-duration-fast) var(--kole-ease-standard),
background-color var(--kole-duration-fast) var(--kole-ease-standard);
}
/* 变体 tone:默认品牌色(可点);default 继承父级文字色(次要链接);danger 用于破坏性操作 */
.kole-m-link--default { color: inherit; }
.kole-m-link--danger { color: var(--kole-color-error); }
.kole-m-link--success { color: var(--kole-color-success); }
/* 变体 underline=true:常驻下划线(正文段落内 / 条款页里必须能一眼分辨) */
.kole-m-link--underline { text-decoration: underline; text-underline-offset: 3px; }
/* 按下反馈:变色 + 极浅底色;不做位移缩放 */
.kole-m-link:active { color: var(--kole-color-brand-active); background: var(--kole-color-brand-bg); }
.kole-m-link--danger:active { background: var(--kole-color-disabled-bg); }
.kole-m-link__label { white-space: nowrap; }
/* 变体 tone=block:撑满容器(列表里整行都可点) */
.kole-m-link.is-block { display: flex; width: 100%; justify-content: flex-start; padding: 0 var(--kole-m-gutter); }
/* 状态 disabled:置灰且不响应;保留 tabindex=-1 以便读屏跳过 */
.kole-m-link.is-disabled,
.kole-m-link:disabled {
color: var(--kole-color-text-disabled);
text-decoration-color: var(--kole-color-text-disabled);
cursor: not-allowed;
}
.kole-m-link.is-disabled:active,
.kole-m-link:disabled:active { background: transparent; }
.kole-m-link:focus-visible {
outline: 2px solid var(--kole-color-focus-ring);
outline-offset: 2px;
}
frameworks-mobile/Link.html · H5 原生(无框架) · 111 行
<!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 · Link(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="Link.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); }
.demo-box { padding: var(--kole-space-4) 0; background: var(--kole-color-card-bg);
border-block: 1px solid var(--kole-color-border); }
.demo-para { margin: 0; padding: 0 var(--kole-m-gutter); font-size: var(--kole-m-font-size-body); line-height: 1.8; }
/* 演示用箭头字形:仅本页样式,不引其它组件类(组件间样式不互相依赖) */
.demo-arrow { display: inline-flex; align-items: center; padding-left: var(--kole-space-4);
color: var(--kole-color-text-placeholder); line-height: 1; }
.demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
<section class="demo-block" data-demo="tone">
<p class="demo-label">语义色(tone=brand 默认 / default 继承父级 / danger 破坏性 / success)</p>
<div class="demo-box" data-assert="link-tone">
<a class="kole-m-link" href="#link-tone">查看订单详情</a>
<a class="kole-m-link kole-m-link--default" href="#link-tone">稍后再说</a>
<a class="kole-m-link kole-m-link--danger" href="#link-tone">删除记录</a>
<a class="kole-m-link kole-m-link--success" href="#link-tone">已完成认证</a>
</div>
</section>
<section class="demo-block" data-demo="underline">
<p class="demo-label">下划线(underline=true:正文段落内必须能一眼分辨出链接)</p>
<div class="demo-box" data-assert="link-underline">
<p class="demo-para">
下单即表示同意
<a class="kole-m-link kole-m-link--underline" href="#link-underline">《用户服务协议》</a>
与
<a class="kole-m-link kole-m-link--underline" href="#link-underline">《隐私政策》</a>。
</p>
</div>
</section>
<section class="demo-block" data-demo="disabled">
<p class="demo-label">禁用(disabled:置灰且不响应;同时不渲染 href,否则读屏仍会把它当可点项)</p>
<div class="demo-box" data-assert="link-disabled">
<button class="kole-m-link kole-m-link--default is-disabled" type="button" disabled aria-disabled="true">退款申请(已超过 7 天)</button>
</div>
</section>
<section class="demo-block" data-demo="block">
<p class="demo-label">整行链接(block=true:撑满容器,热区 44px,列表里整行可点)</p>
<div class="demo-box" data-assert="link-block">
<a class="kole-m-link is-block" href="#link-block">查看全部 12 条评价</a>
</div>
</section>
<section class="demo-block" data-demo="event">
<p class="demo-label">不跳转的链接(无 href 时渲染 button:只回传事件,用于弹窗 / 展开)</p>
<div class="demo-box" data-assert="link-event">
<button class="kole-m-link" type="button" id="link-event-btn"
data-behavior="click-sets-attr:#link-event-btn|data-clicked|true">查看物流</button>
</div>
</section>
<section class="demo-block" data-demo="with-icon">
<p class="demo-label">带图标(图标继承链接颜色,与文字间距 4px;图标自身不是热区)</p>
<div class="demo-box" data-assert="link-with-icon">
<a class="kole-m-link" href="#link-with-icon">
<span class="kole-m-link__label">去填写地址</span>
<span class="demo-arrow" aria-hidden="true">›</span>
</a>
</div>
</section>
</div>
<script>
/* 演示页脚本:不跳转的链接被点击后打标记(真实业务里是打开弹窗 / 展开区块) */
(function () {
var btn = document.getElementById('link-event-btn');
if (!btn) return;
btn.addEventListener('click', function () {
btn.setAttribute('data-clicked', 'true');
});
})();
</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/Link.jsx · React · 50 行
import React from 'react';
import './Link.css';
/* 链接(移动端)— 规格 §21;有 href 时渲染原生 <a>(可键盘聚焦、可长按复制),
无 href 时渲染 <button type="button">(只触发事件、不跳转)。 */
export default function Link({
tone = 'brand',
underline = false,
disabled = false,
href = '',
text = '',
block = false,
onClick,
children = null,
}) {
const cls =
'kole-m-link' +
(tone === 'default' ? ' kole-m-link--default' : '') +
(tone === 'danger' ? ' kole-m-link--danger' : '') +
(tone === 'success' ? ' kole-m-link--success' : '') +
(underline ? ' kole-m-link--underline' : '') +
(block ? ' is-block' : '') +
(disabled ? ' is-disabled' : '');
/* 规格 §21.6:禁用链接不能用 href(否则仍可被打开),且要移出 tab 序列 */
const content = <span className="kole-m-link__label">{text || children}</span>;
if (href && !disabled) {
return (
<a className={cls} href={href} onClick={onClick ? () => onClick() : undefined}>
{content}
</a>
);
}
return (
<button
className={cls}
type="button"
disabled={disabled}
aria-disabled={disabled ? 'true' : 'false'}
onClick={() => {
if (disabled) return;
if (onClick) onClick();
}}
>
{content}
</button>
);
}
frameworks-mobile/Link.vue2.vue · Vue 2 · 49 行
<template>
<a v-if="href && !disabled" class="kole-m-link" :class="linkClass" :href="href" @click="$emit('click')">
<span class="kole-m-link__label"><slot>{{ text }}</slot></span>
</a>
<button v-else class="kole-m-link" :class="linkClass" type="button" :disabled="disabled"
:aria-disabled="disabled ? 'true' : 'false'" @click="onActivate">
<span class="kole-m-link__label"><slot>{{ text }}</slot></span>
</button>
</template>
<script>
var TONE_CLASS = {
default: 'kole-m-link--default',
danger: 'kole-m-link--danger',
success: 'kole-m-link--success'
};
export default {
name: 'KoleMLink',
props: {
tone: { type: String, default: 'brand' },
underline: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
href: { type: String, default: '' },
text: { type: String, default: '' },
block: { type: Boolean, default: false }
},
computed: {
linkClass: function () {
return [
TONE_CLASS[this.tone],
this.underline ? 'kole-m-link--underline' : '',
this.block ? 'is-block' : '',
this.disabled ? 'is-disabled' : ''
].filter(Boolean);
}
},
methods: {
onActivate: function () {
/* 规格 §21.6:禁用链接不触发事件 */
if (this.disabled) return;
this.$emit('click');
}
}
};
</script>
<style src="./Link.css"></style>
frameworks-mobile/Link.vue3.vue · Vue 3 · 42 行
<template>
<a v-if="href && !disabled" class="kole-m-link" :class="linkClass" :href="href" @click="onActivate">
<span class="kole-m-link__label"><slot>{{ text }}</slot></span>
</a>
<button v-else class="kole-m-link" :class="linkClass" type="button" :disabled="disabled"
:aria-disabled="disabled ? 'true' : 'false'" @click="onActivate">
<span class="kole-m-link__label"><slot>{{ text }}</slot></span>
</button>
</template>
<script setup>
import { computed } from 'vue';
const TONE_CLASS = {
default: 'kole-m-link--default',
danger: 'kole-m-link--danger',
success: 'kole-m-link--success'
};
const props = defineProps({
tone: { type: String, default: 'brand' },
underline: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
href: { type: String, default: '' },
text: { type: String, default: '' },
block: { type: Boolean, default: false }
});
const emit = defineEmits(['click']);
const linkClass = computed(() =>
[TONE_CLASS[props.tone], props.underline ? 'kole-m-link--underline' : '', props.block ? 'is-block' : '', props.disabled ? 'is-disabled' : ''].filter(Boolean)
);
function onActivate() {
/* 规格 §21.6:禁用链接不触发事件(渲染层已用 disabled,这里再拦一次) */
if (props.disabled) return;
emit('click');
}
</script>
<style src="./Link.css"></style>
frameworks-mobile/Link.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 78 行
<template>
<view
class="kole-m-link"
:class="linkClass"
role="link"
:aria-disabled="disabled ? 'true' : 'false'"
:data-href="href"
@tap="onActivate"
>
<text class="kole-m-link__label"><slot>{{ text }}</slot></text>
</view>
</template>
<script setup>
/* uni-app 端 · 链接(移动端)— 规格 §21
跨端差异:小程序/App 无 <a>,用 view + role="link";跳转由宿主读 data-href 决定
(组件只 emit click,不自己做路由)。点击用 @tap;尺寸用 rpx(88rpx = 44px @375pt)。 */
import { computed } from 'vue';
const TONE_CLASS = {
default: 'kole-m-link--default',
danger: 'kole-m-link--danger',
success: 'kole-m-link--success'
};
const props = defineProps({
tone: { type: String, default: 'brand' },
underline: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
href: { type: String, default: '' },
text: { type: String, default: '' },
block: { type: Boolean, default: false }
});
const emit = defineEmits(['click']);
const linkClass = computed(() =>
[
TONE_CLASS[props.tone],
props.underline ? 'kole-m-link--underline' : '',
props.block ? 'is-block' : '',
props.disabled ? 'is-disabled' : ''
].filter(Boolean)
);
function onActivate() {
/* 规格 §21.6:禁用链接不触发事件 */
if (props.disabled) return;
emit('click');
}
</script>
<style>
.kole-m-link {
display: flex;
align-items: center;
box-sizing: border-box;
min-height: 88rpx;
padding: 0 8rpx;
border-radius: var(--kole-radius-small);
background-color: transparent;
color: var(--kole-color-brand);
font-size: var(--kole-m-font-size-body);
text-decoration: none;
}
.kole-m-link--default { color: inherit; }
.kole-m-link--danger { color: var(--kole-color-error); }
.kole-m-link--success { color: var(--kole-color-success); }
.kole-m-link--underline { text-decoration: underline; }
.kole-m-link.is-block { width: 100%; padding: 0 32rpx; }
.kole-m-link.is-disabled { color: var(--kole-color-text-disabled); }
.kole-m-link__label { line-height: 1.4; }
</style>
测试与回归
断言在真实的 375×640 设备帧里跑(引擎与 PC 侧共用 tests/_runtime.js,触控行为动词来自移动端 tests/mobile/_behaviors.js)。
断言 17 条 · 全部通过 报告 2026-09-20 16:50:33
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-link.json(点击展开原始 JSON)
{
"schemaVersion": 1,
"sourceKind": "authored-spec",
"provenance": "authored-in-repo",
"specFile": "spec/移动端规格.md",
"specSection": "21 · 链接 Link",
"confidence": "high",
"slug": "mobile-link",
"name": "链接 Link",
"semanticTypeCandidates": [
"link",
"anchor"
],
"variantDimensions": [
{
"name": "tone",
"values": [
"brand",
"default",
"danger",
"success"
]
},
{
"name": "underline",
"values": [
"false",
"true"
]
},
{
"name": "block",
"values": [
"false",
"true"
]
}
],
"representativeVariants": [
{
"tone": "brand",
"underline": "false",
"label": "品牌色链接(默认,行内主入口)"
},
{
"tone": "default",
"underline": "false",
"label": "次要链接(继承父级文字色)"
},
{
"tone": "danger",
"underline": "false",
"label": "破坏性操作链接"
},
{
"tone": "brand",
"underline": "true",
"label": "正文段落内链接(必须带下划线)"
}
],
"anatomy": {
"link": "根元素,有 href 时是原生 a,无 href 时是原生 button(只回传事件)",
"label": "链接文字,单行不换行(超长由宿主截断)",
"icon": "可选尾部图标,继承链接颜色,与文字间距 4px",
"href": "跳转地址;禁用时不渲染该属性(否则仍可被打开)",
"text": "纯文字快捷入口,与默认插槽二选一"
},
"structurePatterns": {
"tone": "brand 默认 / default 继承父级 / danger 破坏性 / success 完成态",
"underline": "false 默认(靠颜色区分)/ true 常驻下划线(正文段落内)",
"block": "false 内联 / true 撑满容器、整行可点"
},
"usageHints": [
"一段内联文字承载「跳转 / 打开下一级」或「触发一次轻量动作」",
"点击热区高度不小于 44px,文字可短但热区不缩水(内边距撑开)",
"按下反馈为颜色变化,不做位移缩放(避免长按抖动)",
"相邻链接之间至少留 8px 间距,防止误触(同时给出 16px 的更稳选择)",
"禁用链接点击不触发事件,也不跳转"
],
"doNotInvent": [
"外跳协议处理(tel: / mailto: / 唤起 App)",
"已访问状态的样式",
"链接的埋点 / 统计"
],
"unknowns": [
"一屏内链接的最大推荐数量",
"链接与相邻文字的推荐最小间距(同时给出 8px 与 16px 两档)"
],
"interaction": [
"点击热区高度不小于 44px,文字可短但热区不缩水(内边距撑开)",
"按下反馈为颜色变化,不做位移缩放(避免长按抖动)",
"相邻链接之间至少留 8px 间距,防止误触(同时给出 16px 的更稳选择)",
"禁用链接点击不触发事件,也不跳转"
],
"accessibility": [
"有 href 用原生 a(可聚焦、可长按复制、读屏报「链接」);无 href 用原生 button",
"禁用链接不能用 href,且要 aria-disabled=\"true\" + tabindex=\"-1\"(移出 tab 序列)",
"链接文字必须自解释,不要出现孤立的「点击这里」(读屏会脱离上下文朗读)",
"颜色不是唯一线索:正文段落内的链接必须带下划线或图标,避免色觉障碍用户无法识别"
],
"api": {
"source": "implementation",
"note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
"requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
"props": [
{
"name": "tone",
"type": "'brand' | 'default' | 'danger' | 'success'",
"default": "'brand'",
"desc": "变体 tone:brand 默认 / default 继承父级文字色(规格 §21.3)",
"required": false
},
{
"name": "underline",
"type": "boolean",
"default": "false",
"desc": "变体 underline:常驻下划线,正文段落内必须开启(规格 §21.3/§21.6)",
"required": false
},
{
"name": "disabled",
"type": "boolean",
"default": "false",
"desc": "置灰且不响应点击,并移出 tab 序列(规格 §21.4/§21.6)",
"required": false
},
{
"name": "href",
"type": "string",
"default": "''",
"desc": "跳转地址;有值时渲染原生 a,禁用时不渲染该属性(规格 §21.2)",
"required": false
},
{
"name": "text",
"type": "string",
"default": "''",
"desc": "纯文字快捷入口,与默认插槽二选一(规格 §21.2)",
"required": false
},
{
"name": "block",
"type": "boolean",
"default": "false",
"desc": "变体 block:撑满容器、整行可点(规格 §21.3)",
"required": false
}
],
"events": [
{
"name": "click",
"params": "—",
"desc": "点击时触发(disabled 时不触发;有 href 时导航由浏览器完成)"
}
],
"slots": [
{
"name": "default",
"desc": "链接文字,可含图标(规格 §21.2 label)"
}
]
},
"variantClasses": {
"tone": {
"brand": [],
"default": [
".kole-m-link--default"
],
"danger": [
".kole-m-link--danger"
],
"success": [
".kole-m-link--success"
]
},
"underline": {
"false": [],
"true": [
".kole-m-link--underline"
]
},
"block": {
"false": [],
"true": [
".is-block"
]
}
},
"demos": [
{
"id": "tone",
"group": "01 组件类型",
"title": "语义色",
"desc": "默认品牌色;secondary 场景用 default 继承父级文字色;破坏性操作用 danger。",
"variant": "tone=brand | default | danger | success"
},
{
"id": "underline",
"group": "01 组件类型",
"title": "正文段落内链接",
"desc": "段落里的链接必须带下划线 —— 只靠颜色区分会让色觉障碍用户看不出来。",
"variant": "underline=true"
},
{
"id": "disabled",
"group": "02 组件状态",
"title": "禁用",
"desc": "置灰且不响应;同时去掉 href 并移出 tab 序列,否则读屏仍会把它当可点项。",
"variant": "disabled=true"
},
{
"id": "block",
"group": "02 组件状态",
"title": "整行链接",
"desc": "列表里「查看全部」这类入口用 block,整行都可点(热区 44px),不必精准点文字。",
"variant": "block=true"
},
{
"id": "event",
"group": "02 组件状态",
"title": "不跳转的链接",
"desc": "无 href 时渲染 button:只回传事件,用于打开弹窗或展开区块,不做页面跳转。",
"variant": "href=''"
},
{
"id": "with-icon",
"group": "02 组件状态",
"title": "带图标",
"desc": "尾部图标继承链接颜色,与文字间距 4px;图标自身不是热区。",
"variant": "tone=brand + 图标"
}
],
"related": [
{
"slug": "mobile-button",
"why": "触发一个明确动作(提交、确认)用按钮;链接只用于跳转或极轻的次要动作"
},
{
"slug": "cell",
"why": "整行进入下级、且行内有多段信息时用单元格;行内文字短语才用链接"
},
{
"slug": "mobile-icon",
"why": "需要给链接配图标时用它渲染字形;图标不承担点击"
}
]
}