图标
本页内容
icon() 返回一个内联的 <svg>。每个图形都画在同样的 24×24 网格上,是 currentColor 的无填充线条,所以它会继承所处位置的颜色和字号,自己不需要任何样式。
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('check'),
icon('search'),
icon('trash'),
icon('settings'),
)在组件里
图标和其他子元素没什么两样。因为它按 em 定尺寸,不用告诉它旁边的文字有多大,它自己就能配上:
stack({ direction: 'row', gap: 'sm', align: 'center', wrap: true },
button({ color: 'primary' }, icon('download'), '下载'),
button({ variant: 'outline' }, icon('external-link'), '打开'),
button({ size: 'sm', variant: 'soft', color: 'danger' }, icon('trash'), '删除'),
iconButton({ label: '搜索', variant: 'soft', icon: icon('search') }),
)尺寸
默认是 1em,也就是周围文字的大小。想跳出这个尺寸时,size 接受一个令牌或任意 CSS 长度:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('star', { size: 'sm' }),
icon('star'),
icon('star', { size: 'lg' }),
icon('star', { size: '2rem' }),
icon('star', { size: '3rem' }),
)颜色
没有颜色属性。图标用 currentColor 绘制,所以取用所处上下文的颜色——正是这一点让一套图标能在五套配色里通用:
stack({ direction: 'row', gap: 'md', align: 'center' },
text({ style: 'color: var(--su-primary)' }, icon('heart', { size: 'lg' })),
text({ style: 'color: var(--su-success)' }, icon('check-circle', { size: 'lg' })),
text({ style: 'color: var(--su-warning)' }, icon('alert-triangle', { size: 'lg' })),
text({ style: 'color: var(--su-danger)' }, icon('x-circle', { size: 'lg' })),
text({ tone: 'muted' }, icon('info', { size: 'lg' })),
)无障碍名称
图标默认带 aria-hidden,这在绝大多数时候都是对的:紧挨着「删除」二字的图标不该再被播报一遍。只有当意思全靠图标承载时,才给它一个 label,那时它会变成带该名称的 role="img"。
icon('trash') // 装饰性——隐藏
button(icon('trash'), '删除') // 由文字来表达
icon('trash', { label: '删除' }) // 作为图像播报
// 纯图标按钮标注的是按钮,而不是里面那个图形
iconButton({ label: '删除', icon: icon('trash') })填充
filled 把图形涂实,而不是只描边。两种形态用的是同一条路径——只有 fill 属性不同——所以它们的外缘严丝合缝,不可能走样。
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('bell', { size: 'lg' }),
icon('bookmark', { size: 'lg' }),
icon('folder', { size: 'lg' }),
icon('heart', { size: 'lg' }),
icon('star', { size: 'lg' }),
)同样这些名字,填充后:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('bell', { filled: true, size: 'lg' }),
icon('bookmark', { filled: true, size: 'lg' }),
icon('folder', { filled: true, size: 'lg' }),
icon('heart', { filled: true, size: 'lg' }),
icon('star', { filled: true, size: 'lg' }),
)它做成属性而不是另起一套名字,是因为「已填充」几乎总是一种状态——已保存、已点赞、已评分——所以它要的是布尔值,而不是另一个字符串:
icon('heart', { filled: liked })
icon('bookmark', { filled: saved, label: saved ? '已保存' : '保存' })
// 而不是
icon(liked ? 'heart-filled' : 'heart')状态类图形的填充方式不一样,因为它们的记号位于形状内部。把圆涂实会吞掉那个对勾,所以改成把记号从圆里挖出来:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('check-circle', { filled: true, size: 'lg' }),
icon('x-circle', { filled: true, size: 'lg' }),
icon('info', { filled: true, size: 'lg' }),
icon('help', { filled: true, size: 'lg' }),
icon('alert-triangle', { filled: true, size: 'lg' }),
)这几个另带一张画——实心形状加上用 fill-rule: evenodd 挖掉的记号——因为这种挖空没法只靠改一个属性从描边路径得到。外形画在描边的外缘上,所以两种形态最终的轮廓仍然一致。属性还是同一个;某个图形走的是哪套机制,那是它自己的事。
折角箭头压根没有内部可涂——它是一条开放的线——所以它会填成自身三个点所描出的三角形,同时保留把拐角磨圆的那道描边:
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('chevron-up', { filled: true, size: 'lg' }),
icon('chevron-down', { filled: true, size: 'lg' }),
icon('chevron-left', { filled: true, size: 'lg' }),
icon('chevron-right', { filled: true, size: 'lg' }),
)fillableIcons() 列出所有会响应 filled 的图形。没有填充形态的图形会忽略它、继续保持描边——把 eye 涂实会丢掉瞳孔,tag 会丢掉那个孔,所以它们都不假装自己能填。
旋转
spin 让图形转起来——本是为 spinner 准备的,不过也没人拦着你在重新加载时转 refresh。在 prefers-reduced-motion 下它会慢到几乎不动,而不是停下,因为停住的加载图看起来像坏了。
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('spinner', { spin: true, size: 'lg' }),
icon('refresh', { spin: true, size: 'lg' }),
button({ variant: 'soft' }, icon('spinner', { spin: true }), '保存中…'),
)整套图标
名字描述的是画本身,而不是它派的用场——是 x-circle 而不是 error——因为同一张画会用在互不相干的地方,而描述图形的名字在那时依然成立。下面的别名覆盖了常见意图。
品牌标识
这套图标带了八个品牌标识——facebook、google、instagram、linkedin、tiktok、whatsapp、x-twitter 和 youtube。它们同样接受 size 和 label,同样用 currentColor 绘制:
stack({ direction: 'row', gap: 'md', align: 'center', wrap: true },
icon('facebook', { size: 'lg' }),
icon('instagram', { size: 'lg' }),
icon('x-twitter', { size: 'lg' }),
icon('youtube', { size: 'lg' }),
icon('whatsapp', { size: 'lg' }),
button({ variant: 'soft', color: 'neutral' }, icon('linkedin'), '分享'),
)它们是对别人商标的复制,而不是按本库风格画的图形,所以有意破了两条规矩:它们是实心形状而非线条——徽标本来就是这样——而且比例取自品牌,而不是这套网格。filled 对它们没有意义:它们本来就是实心的。
图形来自 Simple Icons,以 CC0 发布。这只覆盖画本身,不覆盖商标:请用它们来指代它们所命名的东西——个人主页链接、分享按钮——而不要用在你自己的产品上。
名字是 x-twitter 而不是 x,因为 x 已经是 close 的别名,关闭按钮突然变成一个徽标会是很难堪的意外。twitter 也指向它。
别名
下面每一个渲染的都是上面列出的某个图形,只是换成你更可能想到的名字:
success → check-circle
warning → alert-triangle
danger, error → x-circle
x, cross → close
question → help
loading → spinner
cog, gears → gear
delete, trash-can → trash
pencil → edit
notification → bell
dots → more-horizontal
bolt, lightning → zap
arrow-back → arrow-left
arrow-forward → arrow-right
cart → shopping-cart
bag → shopping-bag
card → credit-card
cash, money → banknote
delivery, shipping → truck
shop → store
discount, sale → percent
login, sign-in → log-in
logout, sign-out → log-out
map-pin, marker → location
mobile → smartphone
like → thumbs-up
dislike → thumbs-down
comment, message, chat → comment-bubble
ai, magic → sparkles
printer → print
accessibility, a11y → universal-access
twitter → x-twitter
你自己的图标
registerIcons() 可以加一个图形,或者替换一个内置图形。标记就是 <svg> 的内容——画在同样的 24×24 网格上、不加填充,好让 currentColor 能作用到它们。在页面会导入的某个模块里调用一次即可:
import { registerIcons } from 'sitelo/ui'
registerIcons({
logo: '<path d="M4 20 12 4l8 16z"/>',
// 用一个已存在的名字会到处替换掉它,想给内置图标换个画法又不想 fork 本库,
// 就是这么做的。
check: '<path d="m5 13 4 4 10-11"/>',
// 单个闭合形状,这样它就能像内置图标一样响应 `filled`。
pin: { markup: '<path d="M12 21s7-6.3 7-11a7 7 0 1 0-14 0c0 4.7 7 11 7 11z"/>', fillable: true },
})import { icon } from 'sitelo/ui'
icon('logo') // 你的图形
icon('check') // 现在也是你的了
registerIcons({ check: null }) // 再换回内置的为什么用内联,而不是雪碧图
图标是渲染进页面的,而不是用 <use> 从一个 icons.svg 里取。雪碧图每页大约省下上百个 gzip 后的 HTML 字节,代价却是一次往返请求——重复的标记恰恰是 gzip 最擅长的场景,所以雪碧图想去重的东西,大半已经被去过重了。内联还意味着没有文件要产出、没有 base 路径要配置,也没有东西会从 dist 里丢失——和 styles({ inline: true }) 做的是同一笔交易。
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | string | — | 要哪个图形。也可以直接作为第一个参数传入。 |
size | 'sm' | 'md' | 'lg' | string | 'md' | 一个令牌,或任意 CSS 长度。默认是 1em。 |
label | string | — | 以这个名字作为图像播报,而不是隐藏它。 |
spin | boolean | false | 让它持续旋转。 |
filled | boolean | false | 把图形涂实而不是描边。不可填充的图形会忽略它。 |
未知的名字会什么都不渲染,而不是抛错——一个纯装饰的属性不该有本事让构建失败。hasIcon(name) 告诉你某个图标存不存在,iconNames() 列出全部,而 fillableIcons() 列出接受 filled 的那些。