关于 hebrew-rtl-best-practices
用于构建希伯来语 RTL 界面的设计技能,帮助页面正确处理从右向左的布局。内容涵盖方向设置、双向文本、图标、字体排版和框架配置。
主要功能
- 01设置文档的 RTL 方向
- 02使用 CSS 逻辑属性
- 03处理希伯来语与 LTR 混排文本
- 04仅镜像具有方向性的图标
- 05希伯来语排版与框架指南
适用场景
- 01构建希伯来语 RTL 网页
- 02修复表单和内容中的混合方向文本
- 03调整 RTL 界面的导航图标和布局
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ skills-il/ localization --skill hebrew-rtl-best-practices安装后在 Claude Code 中输入/hebrew-rtl-best-practices确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evidence.jsonmetadata.jsonoptimization-log.jsonSKILL_HE.mdreferences/css-logical-properties.mddomain-checklist.md详细介绍
希伯来语 RTL 最佳实践
说明
步骤 1:设置文档方向
始终从 HTML 属性开始(而不仅仅是 CSS):
<html lang="he" dir="rtl">
这会告诉浏览器、屏幕阅读器和 CSS 将 RTL 作为基础方向。
步骤 2:使用 CSS 逻辑属性
切勿使用物理方向属性进行布局:
| 物理属性(避免) | 逻辑属性(使用) |
|---|---|
margin-left | margin-inline-start |
margin-right | margin-inline-end |
padding-left | padding-inline-start |
padding-right | padding-inline-end |
border-left | border-inline-start |
text-align: left | text-align: start |
text-align: right | text-align: end |
float: left | float: inline-start |
left: 10px | inset-inline-start: 10px |
这可确保布局在 RTL 模式下自动镜像。
当你确实需要逻辑属性无法表达的特定方向规则时,优先使用 :dir() 伪类而非 [dir="rtl"] 属性选择器:
/* Modern: matches the resolved direction, including dir="auto" and inheritance */
.chevron:dir(rtl) { transform: scaleX(-1); }
/* Older approach: only matches an explicit dir attribute on/above the element */
[dir="rtl"] .chevron { transform: scaleX(-1); }
:dir() 是 Selectors Level 4 的一部分,它解析的是计算后的方向,因此对于那些方向来自 dir="auto" 或祖先元素的元素也适用,而属性选择器则会遗漏这些情况。浏览器支持:Chrome 和 Edge 从 120 版本(2023 年底)开始支持,Firefox 已支持多年,Safari 从 16.4 开始支持,因此现在已是 Baseline(广泛可用)。如需支持旧版浏览器,请保留 [dir="rtl"] 回退规则,或改用逻辑属性。在 https://caniuse.com/css-dir-pseudo 查看当前支持情况。
步骤 3:处理双向文本
当混合希伯来语和英语/数字时:
/* Isolate embedded LTR content */
.ltr-content {
unicode-bidi: isolate;
direction: ltr;
}
/* Mixed or unknown-direction content: each paragraph picks its own base direction */
.user-content {
unicode-bidi: plaintext;
}
不要对混合内容使用 unicode-bidi: bidi-override。它会关闭双向算法的隐式部分,并严格按照 direction 值排列每个字符,因此在 RTL override 中的英语会以字母反转的方式呈现。请将其(或 <bdo>)保留用于故意强制已知的视觉顺序。
常见的双向问题:
- 用空格书写的电话号码(
050 321 4450)或带有+972前缀的电话号码显示混乱:用<bdo dir="ltr">包裹。普通的带连字符的050-321-4450本身就能保持完整,因为两个数字之间的单个连字符会将它们连接成一个数字串。 - 标点出现在句子的错误一端:使用
unicode-bidi: isolate - 希伯来语文本中的 URL/电子邮件:用
<span dir="ltr">包裹
数字与日期: 希伯来语文本中独立的数字和 DD/MM/YYYY 日期通常能正常显示,因为数字是弱 LTR 的,但紧跟着符号、货币或第二个数字的数字可能会翻转。当某个值必须保持固定的视觉顺序时,用 <span dir="ltr"> 或 unicode-bidi: isolate 将其隔离,而不要依赖默认的双向解析。
先格式化值,再隔离它。 双向隔离只能阻止正确的字符串发生翻转;它并不能生成正确的字符串。使用 Intl 进行格式化,然后隔离:谢克尔金额使用 Intl.NumberFormat('he-IL', { style: 'currency', currency: 'ILS' }),日期使用 Intl.DateTimeFormat('he-IL'),如果输出以内联方式出现在希伯来语散文中,则用 <bdi>(或 unicode-bidi: isolate)包裹。不要对 he-IL 输出强制使用 dir="ltr":货币字符串自带从右到左标记(U+200F),并且是为 RTL 上下文布局的,因此强制 LTR 会将 ₪ 移到数字的另一侧。区别在于:对不含方向标记的原始数字字符串(如电话号码)强制使用 dir="ltr",而只对确实含有方向标记的 Intl 输出进行隔离。开发者常常混淆这两者,对格式化 bug 应用双向修复(或反之)。
希伯来语日期需要日历扩展。 Intl.DateTimeFormat('he-IL') 会解析为公历(resolvedOptions().calendar === 'gregory'),因此仅指定希伯来语区域设置并不会得到希伯来语日期。应通过 -u-ca- Unicode 扩展请求日历:Intl.DateTimeFormat('he-IL-u-ca-hebrew') 会将 2026 年 9 月 20 日格式化为 9 בתשרי 5787。在节日、忌日以及双日期显示中使用它,而对于用户需向官方机构提交的任何内容,保留公历格式。
表单输入需要 dir="auto"。 为每个 <input> 和 <textarea> 添加 dir="auto",让每个值自行解析其基础方向。这是最明显的终端用户 RTL 缺陷:如果没有它,输入到希伯来语表单中的电子邮件或英文单词会跳到错误的一侧。请注意,占位符不会触发自动检测,因此如果空字段的外观很重要,请用 CSS 设置静止时的方向。有两个例外。type="tel" 输入默认已是 LTR(HTML 规范规定没有 dir 的 tel 输入具有 LTR 方向性),因此不要给它设置 dir。对于 email 和 url 字段,大多数值是拉丁字符,因此应有意设置 dir="ltr":使用 dir="auto" 时,以希伯来语开头的值(例如希伯来文域名 .ישראל 上的地址)会在输入过程中将字段翻转为 RTL。要权衡这一点与其代价:dir="ltr" 也会让希伯来语占位符左对齐。
<bdi> 与 <bdo>: 仅当你想强制某个方向(它会覆盖双向算法)时才使用 <bdo dir="ltr">。对于用户生成或方向未知的内容,优先使用 <bdi>,它会隔离内容,使其方向自动检测,且不会泄漏到周围文本中:
<p>שלום, <bdi>{{ userName }}</bdi>, ברוך הבא</p>
在嵌入片段上同时设置 dir 和 lang。 dir 只能修正视觉顺序。将希伯来语正文中的英文术语标记为 <span lang="en" dir="ltr">:语音合成器和盲文翻译器会使用语言标签来切换语言模式,:lang() 字体和断词规则也以此为依据。没有它,辅助技术就没有信号在该片段中离开希伯来语模式。
对于自由文本字段,dir="auto"(或 CSS 中的 unicode-bidi: plaintext)让浏览器按每个值选择基础方向,这是评论、姓名和搜索查询在事先不知道语言时的正确默认值。
阴影和渐变不会自动翻转。 CSS 逻辑属性会镜像布局,但 box-shadow、text-shadow 和 linear-gradient 的偏移/角度是物理的,在方向翻转时保持不变。在 LTR 中看起来正确的 4px 4px 阴影偏移,在相对于 RTL 布局时会指向"错误"的方向。同样的物理而非逻辑陷阱也适用于 transform-origin、background-position 以及基于 translateX 的关键帧动画(滑入式抽屉、轮播、进度微光)。当其方向有意义时,用 :dir(rtl)(或 [dir="rtl"])覆盖显式翻转每一个。
第 4 步:镜像方向性图标
图标镜像是最常见的 RTL 缺陷之一。规则是:只镜像那些含义与阅读方向相关的图标,其余的一律保持原样。
需要镜像的图标(它们的方向编码了相对于阅读顺序的“前进/后退/下一个/上一个”):
- 导航箭头、后退/前进按钮、面包屑箭头
- “发送”/提交箭头、轮播和分页箭头
- 缩进、列表嵌套和回复箭头
- 暗示向前运动的进度指示器
不要镜像这些图标(镜像会使它们错误或无法辨认):
- Logo 和品牌标识
- 对勾和 X / 关闭图标
- 媒体播放按钮(播放按钮始终指向右侧,它指代的是时间轴,而非阅读方向)
- 时钟和模拟时间图标(顺时针是通用的)
- 描绘具有固定朝向的真实世界物体的图标(电话听筒、带手柄的放大镜、大多数产品图标)
技巧:使用水平翻转变换来镜像:
/* Flip only when the document direction is RTL */
.icon-directional:dir(rtl) { transform: scaleX(-1); }
<button class="rtl:-scale-x-100">
<ArrowLeftIcon />
</button>
许多图标集(例如 Material Symbols)已经提供了 RTL 感知变体,在可用时应优先使用这些变体而不是翻转,因为翻转后的图标可能会错误渲染精细细节或内嵌文字。
第 5 步:希伯来语排版
推荐的字体栈:
font-family: 'Heebo', 'Assistant', 'Rubik', 'Noto Sans Hebrew', sans-serif;
排版设置:
html[dir="rtl"] body {
line-height: 1.7;
letter-spacing: normal; /* no tracking on running text */
}
字间距是希伯来语的一种强调手段,因此不要全局禁用。 正文保持 letter-spacing: normal,但希伯来语排版会有意使用字间距(字距调整)来强调名称、术语和概念。在 RTL 主题中一刀切地使用 letter-spacing: 0 !important 重置会抹掉这种合理的强调。
希伯来语没有字母大小写。 广泛使用的现代有大小写文字系统是拉丁字母、希腊字母、亚美尼亚字母和西里尔字母,希伯来语不在其中,因此 text-transform: uppercase / capitalize 和 font-variant: small-caps 对希伯来字母没有任何效果。但它们对同一元素中内嵌的拉丁单词并非没有效果:一个共享的设计系统按钮或标题若将标签转为大写,希伯来语部分不受影响,旁边的 “GMAIL” 或 “PDF” 却被放大成大写。应当从 RTL 主题中移除大小写变换,而不是想当然地认为它们不起作用。
Nikkud 和 cantillation 标记需要垂直空间。 元音符号是组合标记,位于基础字母的下方或上方,会扩大实际字形框。紧凑的 line-height(1.2 或更小)会裁剪它们或使其与上一行碰撞,这正是此处推荐 1.7 的实际原因。一些以拉丁字母优先的网页字体所附带的希伯来字母也不包含 nikkud 字形,因此在发布前应使用实际字体测试任何带注音符号的文本(礼拜文本、儿童内容、词典)。
第 6 步:框架专属设置
Tailwind CSS RTL(v4,当前版本;自 v3.3 起支持逻辑属性工具类):
优先使用逻辑属性工具类,而非 rtl:/ltr: 变体:
| 物理类 | 逻辑类 | CSS 属性 |
|---|---|---|
ml-4 | ms-4 | margin-inline-start |
mr-4 | me-4 | margin-inline-end |
pl-4 | ps-4 | padding-inline-start |
pr-4 | pe-4 | padding-inline-end |
left-4 | inset-s-4(原为 start-4) | inset-inline-start |
right-4 | inset-e-4(原为 end-4) | inset-inline-end |
rounded-l-lg | rounded-s-lg | border-start-start-radius + border-end-start-radius |
rounded-r-lg | rounded-e-lg | border-start-end-radius + border-end-end-radius |
<div class="ltr:ml-4 rtl:mr-4">...</div>
<div class="ms-4">...</div>
仅在逻辑属性无法处理的情况下(例如方向性图标、变换)才保留 rtl: / ltr: 变体。
部分工具类没有逻辑形式,在 v4 中仍为物理属性:translate-x-*(滑入式抽屉和面板)、origin-left / origin-right,以及 bg-linear-to-r / bg-linear-to-l。为每个都搭配一个 rtl: 覆盖,例如对从起始边缘进入的抽屉使用 -translate-x-full rtl:translate-x-full。
Tailwind v4 注意事项: v4(自 2025 年初正式发布,当前为 v4.3)使用 CSS 优先配置(在 CSS 中 @import "tailwindcss"),而非 tailwind.config.js。逻辑工具类在 v3 和 v4 中行为完全相同。自 v4.2(2026 年 2 月)起,逻辑 inset 工具类 start-*/end-* 已废弃,改用 inset-s-*/inset-e-*(旧名称仍可用;尚未公布移除日期);margin/padding 工具类 ms-*/me-*/ps-*/pe-* 不受影响。
Next.js App Router:
// app/[locale]/layout.tsx (a plain app/layout.tsx receives no locale param)
import { Heebo } from 'next/font/google';
const heebo = Heebo({
subsets: ['hebrew', 'latin'],
weight: ['400', '500', '700'],
});
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const isRTL = locale === 'he';
return (
<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'}>
<body className={heebo.className}>{children}</body>
</html>
);
}
next/font 自行托管字体(不向外部 Google Fonts 发起请求,零布局偏移)。
使用 MUI 的 React:
当前 MUI(截至 2026 年为 v9)使用官方 fork @mui/stylis-plugin-rtl,而非较旧的社区包 stylis-plugin-rtl。官方 fork 修复了 CSS layers 问题并支持当前 Stylis 版本;自 MUI v6 起这已是推荐配置。
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import rtlPlugin from '@mui/stylis-plugin-rtl';
import { prefixer } from 'stylis';
const cacheRtl = createCache({
key: 'muirtl',
stylisPlugins: [prefixer, rtlPlugin],
});
const theme = createTheme({ direction: 'rtl' });
在 Next.js App Router 中,将相同选项传给 MUI 的 AppRouterCacheProvider(来自 @mui/material-nextjs/v16-appRouter,与你的 Next.js 主版本匹配),而不是构建单独的 cache:<AppRouterCacheProvider options={{ key: 'muirtl', stylisPlugins: [prefixer, rtlPlugin] }}>,并将 <ThemeProvider theme={theme}> 放在其中。它的 options prop 会直接传给 Emotion 的 createCache。
@mui/stylis-plugin-rtl 仅暴露一个 default 导出,因此具名 import { rtlPlugin } 能编译通过但结果为 undefined,Emotion 会静默跳过该插件:应用看起来是 LTR 却没有任何报错。请根据当前 MUI RTL 指南(https://mui.com/material-ui/customization/right-to-left/)核对你所用 MUI 版本的准确导入名称和设置方式。
通过 portal 渲染的 UI(模态框、下拉菜单、工具提示、toast)。 通过 portal 渲染的组件(React createPortal、Radix、MUI Menu、Floating UI)挂载在 document.body 上,并从此处继承方向,但许多库默认假设为 LTR。请在 <html> 上设置 dir,并且同时传入库自身的方向设置:Radix 需要 <DirectionProvider dir="rtl"> 包裹,MUI 需要在 theme 中设置 direction: 'rtl'。否则即使页面其余部分正确,弹出层也会在错误的一侧打开。
第 7 步:需要检查的常见陷阱
- 方向性图标——将它们镜像(请参阅第 4 步,了解哪些图标需要翻转,哪些应保持不变)
- 进度条——应从右向左填充
- 滑块/轮播——滑动方向应反转
- 表单标签——应右对齐
- 面包屑——分隔符方向应反转
- 表格——列会自动重新排序,但要用
<td dir="ltr">强制将数字、代码和日期单元格恢复为 LTR,并让它们保持一致对齐(例如text-align: end;仅靠对齐不会改变方向) - 图表——对于希伯来语读者,x 轴可能需要反转(SVG 没有逻辑属性,因此使用图表库的
reversed/rtl选项,而不是 CSS) - 阴影和渐变——物理偏移/角度不会自动翻转(请参阅第 3 步)
- 固定和粘性界面元素(页眉、Toast、FAB、抽屉)——硬编码的
left: 0/right: 0不会翻转;请使用inset-inline-start/inset-inline-end - 在 RTL 中滚动条位于左侧——使用
scrollbar-gutter: stable(Tailwind v4.3+:scrollbar-gutter-stable)预留空间,以避免重排;text-wrap: balance可改善希伯来语标题
第 8 步:验证 RTL 布局
仅靠编写规则还不够,发布前必须验证:
- 将整个应用切换为
dir="rtl",并检查任何没有移动的内容(它仍在使用物理属性)。 - 在每个文本表面上测试一个规范的混合字符串:
שלום John +972 50-123-4567 ₪1,234一次性检验希伯来语、拉丁字母、国际电话号码和货币金额。 - 打开每个模态框、下拉菜单、工具提示和 Toast(传送式 UI 是最常见的 RTL 遗漏点)。
- 检查固定/粘性界面元素、图表/SVG,以及带有
dir="auto"的表单字段。 - 自动化它:在 Playwright 中于
dir="rtl"和dir="ltr"下渲染相同页面两次,并对它们进行截图差异比对。手动翻转是团队最先跳过的步骤,而悄悄重新混入的物理属性会在用户报告之前很早就以像素差异的形式显现出来。
示例
示例 1:将 LTR 组件转换为 RTL
用户说:“让这个卡片组件支持希伯来语”
转换前(仅 LTR):
.card {
margin-left: 16px;
padding-right: 12px;
text-align: left;
border-left: 3px solid blue;
}
转换后(兼容 RTL):
.card {
margin-inline-start: 16px;
padding-inline-end: 12px;
text-align: start;
border-inline-start: 3px solid blue;
}
使用 Tailwind 时,将 ml-4 pr-3 text-left border-l-4 替换为 ms-4 pe-3 text-start border-s-4。
示例 2:双向文本问题
用户说:“我的希伯来语文本中的数字显示反了”
<p>התקשרו אלינו: 050 321 4450</p>
<p>התקשרו אלינו: +972 50-321-4450</p>
<p>התקשרו אלינו: <span dir="ltr">+972 50-321-4450</span></p>
像 050-321-4450 这样仅含连字符的数字无需额外处理也能正确渲染,因此在归咎于双向文本之前,请先检查实际格式。对于仅 CSS 的解决方案,在包含它的 span 上使用 unicode-bidi: isolate 和 direction: ltr。
示例 3:Tailwind RTL 导航
用户说:“我的侧边栏在希伯来语中位于错误的一侧”
<aside class="fixed left-0 w-64">...</aside>
<aside class="fixed inset-s-0 w-64">...</aside>
<button class="rtl:-scale-x-100">
<ArrowLeftIcon />
</button>
捆绑资源
参考
references/css-logical-properties.md- 完整的物理 CSS 属性到逻辑 CSS 属性映射表(外边距、内边距、边框、定位、文本对齐、尺寸),以及针对无衬线、衬线和等宽字体的希伯来语字体栈建议。在将任何 LTR 样式表转换为兼容 RTL 的逻辑属性,或选择希伯来语 Web 字体时查阅。
注意事项
- CSS 的
text-align: left对希伯来语来说是错误的。应使用text-align: start,它会遵循文档方向。Agent 经常在 CSS 中硬编码left对齐。 margin-left和padding-right在 RTL 模式下不会翻转。应改用 CSS 逻辑属性:margin-inline-start和padding-inline-end。基于 LTR CSS 训练的 Agent 会生成物理属性。- Flexbox 的
row方向在 RTL 中会自动反转,但row-reverse也会反转,导致双重翻转又回到 LTR 顺序。Agent 可能会添加row-reverse,以为这样能创建 RTL,但实际上它在 RTL 上下文中创建了 LTR。 - 电话号码、信用卡号、代码、文件路径和命令行命令即使在 RTL 容器内也必须保持 LTR。将内联值包裹在
<bdo dir="ltr">中,并在 RTL 主题中一次性设置code, pre, kbd, samp { direction: ltr; unicode-bidi: isolate; },这样每个代码块和键盘快捷键都能覆盖到。Agent 经常让这些继承 RTL,从而把末尾的斜杠或标志移到错误的一端。 - 在 RTL 滚动容器中,
scrollLeft在起始(最右)位置为0,向末尾方向会变得越来越负。把自定义轮播和"下一个"按钮写成el.scrollLeft += 300,或用Math.max(0, ...)进行钳制,会导致滚动方向错误或卡在起始位置。应使用el.scrollBy({ left: isRtl ? -300 : 300 }),并将scrollLeft <= 0视为正常的 RTL 范围。
参考链接
| 来源 | URL | 检查内容 |
|---|---|---|
| MDN CSS 逻辑属性 | https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Logical_properties_and_values | 完整属性列表、浏览器支持表 |
MDN :dir() 伪类 | https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:dir | 语法、与 [dir] 属性选择器的行为对比 |
Can I use: :dir() | https://caniuse.com/css-dir-pseudo | 当前浏览器支持表 |
MDN <bdi> 元素 | https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/bdi | 隔离用户生成的双向文本内容 |
| Tailwind CSS RTL 支持 | https://tailwindcss.com/docs/hover-focus-and-other-states#rtl-support | rtl: / ltr: 变体语法 |
| Tailwind 逻辑属性 | https://tailwindcss.com/docs/margin | ms-*、me-*、ps-*、pe-* 工具类 |
| MUI 从右到左 | https://mui.com/material-ui/customization/right-to-left/ | 当前 MUI 的 @mui/stylis-plugin-rtl 设置 |
| Google Fonts 希伯来语 | https://fonts.google.com/?lang=he_Hebr | 可用的希伯来语字体族 |
| W3C 国际化 | https://www.w3.org/International/articles/inline-bidi-markup/ | Unicode 双向算法、标记最佳实践 |
故障排查
错误:"文本对齐看起来不对"
原因:使用了 text-align: left 而不是 text-align: start
解决方案:将 text-align 中所有的 left/right 替换为 start/end。
错误:"布局没有镜像"
原因:使用了物理 margin/padding 而不是逻辑属性
解决方案:将所有的 margin-left/margin-right 替换为 margin-inline-start/margin-inline-end。