picasso-skill 1.6.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -32
- package/agents/picasso.md +14 -2
- package/commands/backlog.md +34 -0
- package/commands/variants.md +18 -0
- package/package.json +1 -1
- package/references/animation-performance.md +244 -0
- package/references/brand-and-identity.md +136 -0
- package/references/code-typography.md +222 -0
- package/references/dark-mode.md +199 -0
- package/references/i18n-visual-patterns.md +177 -0
- package/references/images-and-media.md +222 -0
- package/references/loading-and-states.md +258 -0
- package/references/micro-interactions.md +291 -0
- package/references/navigation-patterns.md +247 -0
- package/references/tables-and-forms.md +227 -0
- package/skills/picasso/SKILL.md +35 -2
- package/skills/picasso/references/animation-performance.md +244 -0
- package/skills/picasso/references/brand-and-identity.md +136 -0
- package/skills/picasso/references/code-typography.md +222 -0
- package/skills/picasso/references/dark-mode.md +199 -0
- package/skills/picasso/references/i18n-visual-patterns.md +177 -0
- package/skills/picasso/references/images-and-media.md +222 -0
- package/skills/picasso/references/loading-and-states.md +258 -0
- package/skills/picasso/references/micro-interactions.md +291 -0
- package/skills/picasso/references/navigation-patterns.md +247 -0
- package/skills/picasso/references/tables-and-forms.md +227 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Code Typography Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
1. Monospace Font Selection
|
|
5
|
+
2. Code Block Design
|
|
6
|
+
3. Syntax Highlighting Accessibility
|
|
7
|
+
4. Copy-to-Clipboard
|
|
8
|
+
5. Responsive Code Blocks
|
|
9
|
+
6. Inline Code Styling
|
|
10
|
+
7. Diff Views
|
|
11
|
+
8. Terminal Output
|
|
12
|
+
9. Common Mistakes
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Monospace Font Selection
|
|
17
|
+
|
|
18
|
+
| Font | Ligatures | Style | Best For |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| JetBrains Mono | Yes | Clean, geometric | General purpose, IDEs |
|
|
21
|
+
| Fira Code | Yes | Slightly rounded | Tutorials, docs |
|
|
22
|
+
| Source Code Pro | No | Adobe, professional | Enterprise, clean look |
|
|
23
|
+
| IBM Plex Mono | No | Corporate, legible | Documentation |
|
|
24
|
+
| Geist Mono | No | Vercel, modern | Next.js projects |
|
|
25
|
+
| Cascadia Code | Yes | Microsoft, playful | Terminals |
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
code, pre, .mono {
|
|
29
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
|
|
30
|
+
font-feature-settings: 'liga' 1, 'calt' 1; /* enable ligatures */
|
|
31
|
+
font-variant-ligatures: common-ligatures;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Ligatures: `=>` becomes ⇒, `!==` becomes ≢, `>=` becomes ≥. Enable for display, disable for editing if users copy code.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. Code Block Design
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
pre {
|
|
43
|
+
background: var(--surface-1);
|
|
44
|
+
border: 1px solid var(--border);
|
|
45
|
+
border-radius: 8px;
|
|
46
|
+
padding: 1rem 1.25rem;
|
|
47
|
+
font-size: 0.875rem; /* 14px — slightly smaller than body */
|
|
48
|
+
line-height: 1.65; /* Looser than body text for readability */
|
|
49
|
+
overflow-x: auto;
|
|
50
|
+
tab-size: 2;
|
|
51
|
+
-moz-tab-size: 2;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Dark mode code blocks on light sites */
|
|
55
|
+
[data-theme="light"] pre {
|
|
56
|
+
background: oklch(0.14 0.02 230);
|
|
57
|
+
color: oklch(0.90 0.01 230);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Line numbers (optional):
|
|
62
|
+
```css
|
|
63
|
+
pre.line-numbers {
|
|
64
|
+
counter-reset: line;
|
|
65
|
+
padding-left: 3.5rem;
|
|
66
|
+
position: relative;
|
|
67
|
+
}
|
|
68
|
+
pre.line-numbers .line::before {
|
|
69
|
+
counter-increment: line;
|
|
70
|
+
content: counter(line);
|
|
71
|
+
position: absolute;
|
|
72
|
+
left: 1rem;
|
|
73
|
+
color: var(--text-muted);
|
|
74
|
+
font-size: 0.75rem;
|
|
75
|
+
user-select: none; /* don't copy line numbers */
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 3. Syntax Highlighting Accessibility
|
|
82
|
+
|
|
83
|
+
Every token color must have **minimum 3:1 contrast** against the code block background. Don't rely on color alone — use font-weight or font-style for emphasis.
|
|
84
|
+
|
|
85
|
+
| Token Type | Suggested OKLCH (dark bg) | Purpose |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Keywords | `oklch(0.75 0.15 300)` | purple-ish, bold |
|
|
88
|
+
| Strings | `oklch(0.72 0.14 150)` | green |
|
|
89
|
+
| Numbers | `oklch(0.75 0.12 60)` | amber |
|
|
90
|
+
| Comments | `oklch(0.50 0.01 230)` | muted, italic |
|
|
91
|
+
| Functions | `oklch(0.78 0.10 230)` | blue |
|
|
92
|
+
| Variables | `oklch(0.85 0.01 230)` | near-white |
|
|
93
|
+
|
|
94
|
+
```css
|
|
95
|
+
.token-keyword { color: oklch(0.75 0.15 300); font-weight: 600; }
|
|
96
|
+
.token-string { color: oklch(0.72 0.14 150); }
|
|
97
|
+
.token-comment { color: oklch(0.50 0.01 230); font-style: italic; }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 4. Copy-to-Clipboard
|
|
103
|
+
|
|
104
|
+
Position: top-right corner of the code block. Show on hover. Provide visual feedback.
|
|
105
|
+
|
|
106
|
+
```jsx
|
|
107
|
+
function CopyButton({ code }) {
|
|
108
|
+
const [copied, setCopied] = useState(false);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<button
|
|
112
|
+
onClick={() => {
|
|
113
|
+
navigator.clipboard.writeText(code);
|
|
114
|
+
setCopied(true);
|
|
115
|
+
setTimeout(() => setCopied(false), 2000);
|
|
116
|
+
}}
|
|
117
|
+
className="absolute top-2 right-2 p-1.5 rounded-md bg-white/5 hover:bg-white/10 text-xs text-muted"
|
|
118
|
+
aria-label="Copy code"
|
|
119
|
+
>
|
|
120
|
+
{copied ? 'Copied' : 'Copy'}
|
|
121
|
+
</button>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 5. Responsive Code Blocks
|
|
129
|
+
|
|
130
|
+
Code blocks should **scroll horizontally** on mobile, never wrap. Terminal output CAN wrap.
|
|
131
|
+
|
|
132
|
+
```css
|
|
133
|
+
pre {
|
|
134
|
+
overflow-x: auto;
|
|
135
|
+
white-space: pre; /* code: no wrap */
|
|
136
|
+
-webkit-overflow-scrolling: touch; /* smooth scroll on iOS */
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pre.terminal {
|
|
140
|
+
white-space: pre-wrap; /* terminal: wrap is OK */
|
|
141
|
+
word-break: break-all;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Hide scrollbar but keep functionality */
|
|
145
|
+
pre::-webkit-scrollbar { height: 4px; }
|
|
146
|
+
pre::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
On very small screens (< 375px), consider reducing code font-size to 12px.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 6. Inline Code Styling
|
|
154
|
+
|
|
155
|
+
Inline code needs subtle visual distinction from body text:
|
|
156
|
+
|
|
157
|
+
```css
|
|
158
|
+
code:not(pre code) {
|
|
159
|
+
background: var(--surface-2);
|
|
160
|
+
padding: 0.15em 0.4em;
|
|
161
|
+
border-radius: 4px;
|
|
162
|
+
font-size: 0.9em; /* slightly smaller than surrounding text */
|
|
163
|
+
font-weight: 500;
|
|
164
|
+
border: 1px solid var(--border);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Never use inline code for emphasis. It's for code references (`useState`, `border-radius`, `GET /api/users`), not for highlighting words.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 7. Diff Views
|
|
173
|
+
|
|
174
|
+
Use color + icon, not color alone (colorblind users):
|
|
175
|
+
|
|
176
|
+
```css
|
|
177
|
+
.diff-add {
|
|
178
|
+
background: oklch(0.62 0.19 150 / 0.1);
|
|
179
|
+
border-left: 3px solid oklch(0.62 0.19 150);
|
|
180
|
+
}
|
|
181
|
+
.diff-add::before { content: '+'; color: oklch(0.62 0.19 150); }
|
|
182
|
+
|
|
183
|
+
.diff-remove {
|
|
184
|
+
background: oklch(0.55 0.22 25 / 0.1);
|
|
185
|
+
border-left: 3px solid oklch(0.55 0.22 25);
|
|
186
|
+
text-decoration: line-through;
|
|
187
|
+
opacity: 0.7;
|
|
188
|
+
}
|
|
189
|
+
.diff-remove::before { content: '-'; color: oklch(0.55 0.22 25); }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 8. Terminal Output
|
|
195
|
+
|
|
196
|
+
Terminal/console styling should feel distinct from code:
|
|
197
|
+
|
|
198
|
+
```css
|
|
199
|
+
.terminal {
|
|
200
|
+
background: oklch(0.08 0.01 230);
|
|
201
|
+
color: oklch(0.80 0.01 150); /* slight green tint for terminal feel */
|
|
202
|
+
font-family: 'JetBrains Mono', monospace;
|
|
203
|
+
padding: 1rem;
|
|
204
|
+
border-radius: 8px;
|
|
205
|
+
}
|
|
206
|
+
.terminal .prompt { color: oklch(0.65 0.10 230); } /* blue prompt */
|
|
207
|
+
.terminal .output { color: oklch(0.75 0.01 230); } /* neutral output */
|
|
208
|
+
.terminal .error { color: oklch(0.65 0.22 25); } /* red errors */
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 9. Common Mistakes
|
|
214
|
+
|
|
215
|
+
- **Code font too large.** 14px for blocks, 0.9em for inline. Larger fights with body text.
|
|
216
|
+
- **No horizontal scroll on code blocks.** Wrapping code breaks readability. Always `overflow-x: auto`.
|
|
217
|
+
- **Syntax colors with < 3:1 contrast.** Comments especially — they tend to be too faint.
|
|
218
|
+
- **Color-only diff indication.** Always add + / - markers or icons alongside color.
|
|
219
|
+
- **Copying includes line numbers.** Use `user-select: none` on line number elements.
|
|
220
|
+
- **Same styling for code blocks and terminal.** They serve different purposes. Terminal gets darker bg, green tint.
|
|
221
|
+
- **`font-family: monospace` without named fonts.** Browsers default to Courier New which looks dated. Always specify a modern monospace font first.
|
|
222
|
+
- **No copy button.** Users shouldn't have to triple-click and drag to copy code.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Dark Mode Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
1. Preference Hierarchy
|
|
5
|
+
2. CSS Custom Properties Approach
|
|
6
|
+
3. Mode Transition
|
|
7
|
+
4. Surface Elevation in Dark Mode
|
|
8
|
+
5. Color Adjustments
|
|
9
|
+
6. Image and Media Handling
|
|
10
|
+
7. Testing Dark Mode
|
|
11
|
+
8. Forced Colors and High Contrast
|
|
12
|
+
9. Common Mistakes
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Preference Hierarchy
|
|
17
|
+
|
|
18
|
+
System preference is the default. User override persists via localStorage. Never force dark mode without a toggle.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// Check system preference, then user override
|
|
22
|
+
const stored = localStorage.getItem('theme');
|
|
23
|
+
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
24
|
+
const isDark = stored ? stored === 'dark' : systemDark;
|
|
25
|
+
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
/* System preference as base */
|
|
30
|
+
:root { color-scheme: light dark; }
|
|
31
|
+
|
|
32
|
+
/* Light (default) */
|
|
33
|
+
:root, [data-theme="light"] {
|
|
34
|
+
--surface-0: oklch(0.99 0.005 var(--hue));
|
|
35
|
+
--text-primary: oklch(0.15 0.02 var(--hue));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Dark */
|
|
39
|
+
[data-theme="dark"] {
|
|
40
|
+
--surface-0: oklch(0.11 0.02 var(--hue));
|
|
41
|
+
--text-primary: oklch(0.93 0.01 var(--hue));
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. CSS Custom Properties Approach
|
|
48
|
+
|
|
49
|
+
Define all colors as CSS variables. Never hardcode hex in components. Dark mode is a variable swap, not a rewrite.
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
[data-theme="dark"] {
|
|
53
|
+
--surface-0: oklch(0.11 0.02 230); /* page bg */
|
|
54
|
+
--surface-1: oklch(0.14 0.02 230); /* card bg */
|
|
55
|
+
--surface-2: oklch(0.17 0.022 230); /* elevated bg */
|
|
56
|
+
--surface-3: oklch(0.20 0.024 230); /* hover bg */
|
|
57
|
+
--border: oklch(0.22 0.015 230);
|
|
58
|
+
--text-primary: oklch(0.93 0.01 230);
|
|
59
|
+
--text-secondary: oklch(0.62 0.02 230);
|
|
60
|
+
--text-muted: oklch(0.42 0.015 230);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Mode Transition
|
|
67
|
+
|
|
68
|
+
Never instant-swap. Use a 200ms opacity transition on the body. Disable transitions during the swap to prevent every element animating individually.
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
/* Add this class during theme switch, remove after 200ms */
|
|
72
|
+
.theme-transitioning * {
|
|
73
|
+
transition: none !important;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
body {
|
|
77
|
+
transition: background-color 0.2s ease-out, color 0.2s ease-out;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
function toggleTheme() {
|
|
83
|
+
document.body.classList.add('theme-transitioning');
|
|
84
|
+
const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
85
|
+
document.documentElement.dataset.theme = next;
|
|
86
|
+
localStorage.setItem('theme', next);
|
|
87
|
+
requestAnimationFrame(() => {
|
|
88
|
+
requestAnimationFrame(() => {
|
|
89
|
+
document.body.classList.remove('theme-transitioning');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 4. Surface Elevation in Dark Mode
|
|
98
|
+
|
|
99
|
+
In dark mode, elevated surfaces get LIGHTER, not darker. This is the opposite of light mode where shadows darken surfaces. Without this, dark mode looks flat.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Light mode elevation: surface-0 (lightest) → surface-3 (slightly darker)
|
|
103
|
+
Dark mode elevation: surface-0 (darkest) → surface-3 (slightly lighter)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Shadows are nearly invisible in dark mode. Replace with surface lightness differentiation and subtle border glow:
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
[data-theme="dark"] .card {
|
|
110
|
+
background: var(--surface-1);
|
|
111
|
+
border: 1px solid oklch(1 0 0 / 0.06);
|
|
112
|
+
/* Shadow optional — use border + surface tint instead */
|
|
113
|
+
box-shadow: 0 0 0 1px oklch(1 0 0 / 0.03);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 5. Color Adjustments
|
|
120
|
+
|
|
121
|
+
Accent colors need lower chroma in dark mode. Full-saturation accents on dark backgrounds are harsh.
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
:root { --accent: oklch(0.55 0.25 250); } /* light: saturated */
|
|
125
|
+
[data-theme="dark"] { --accent: oklch(0.65 0.18 250); } /* dark: lighter, less chroma */
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Semantic colors also need adjustment:
|
|
129
|
+
- Success green: lighter, less saturated
|
|
130
|
+
- Error red: lighter to maintain contrast
|
|
131
|
+
- Warning amber: reduce chroma to avoid glowing
|
|
132
|
+
|
|
133
|
+
Text colors: minimum lightness 0.60 in OKLCH for body text on dark backgrounds. Below 0.55 looks washed out on cheap screens even if it passes WCAG.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 6. Image and Media Handling
|
|
138
|
+
|
|
139
|
+
Dim images slightly in dark mode to reduce glare. SVGs should use currentColor or CSS variables.
|
|
140
|
+
|
|
141
|
+
```css
|
|
142
|
+
[data-theme="dark"] img:not([data-no-dim]) {
|
|
143
|
+
filter: brightness(0.9) contrast(1.05);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
[data-theme="dark"] svg { color: var(--text-primary); }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For decorative images, consider `mix-blend-mode: luminosity` to desaturate:
|
|
150
|
+
```css
|
|
151
|
+
[data-theme="dark"] .hero-image {
|
|
152
|
+
mix-blend-mode: luminosity;
|
|
153
|
+
opacity: 0.8;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 7. Testing Dark Mode
|
|
160
|
+
|
|
161
|
+
Dark mode is not "invert the colors." Test these specifically:
|
|
162
|
+
|
|
163
|
+
1. **Contrast ratios** — recheck all text/background pairs. WCAG ratios change.
|
|
164
|
+
2. **Shadows** — are they visible? If not, use border or surface tint instead.
|
|
165
|
+
3. **Form inputs** — autofill styling overrides your dark background. Fix with `-webkit-box-shadow: 0 0 0 1000px var(--surface-1) inset`.
|
|
166
|
+
4. **Selection color** — `::selection` background needs to contrast with dark text highlight.
|
|
167
|
+
5. **Scrollbar** — custom scrollbar thumb must be visible on dark track.
|
|
168
|
+
6. **Third-party embeds** — iframes, maps, payment forms may not respect your dark mode.
|
|
169
|
+
7. **Screenshots** — take a screenshot and look at it on a non-retina screen. Subtle contrast often vanishes.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 8. Forced Colors and High Contrast
|
|
174
|
+
|
|
175
|
+
Windows High Contrast mode (`@media (forced-colors: active)`) overrides ALL custom colors. Your tinted neutrals, subtle borders, and custom focus rings will disappear.
|
|
176
|
+
|
|
177
|
+
```css
|
|
178
|
+
@media (forced-colors: active) {
|
|
179
|
+
.card { border: 1px solid CanvasText; }
|
|
180
|
+
:focus-visible { outline: 2px solid Highlight; }
|
|
181
|
+
.btn-primary { border: 2px solid ButtonText; }
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Never use `forced-color-adjust: none` globally. Only on specific elements where you've provided a system-color fallback.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 9. Common Mistakes
|
|
190
|
+
|
|
191
|
+
- **Instant theme swap without transition.** Jarring. Always fade (200ms).
|
|
192
|
+
- **Shadows that work in light but vanish in dark.** Replace with borders + surface tint.
|
|
193
|
+
- **Same accent chroma in both modes.** Reduce chroma for dark mode.
|
|
194
|
+
- **Checking contrast only in light mode.** Dark mode ratios are different — recheck.
|
|
195
|
+
- **`background: black` for dark mode.** Never pure black. Tint toward your hue.
|
|
196
|
+
- **Not testing autofill.** Browsers apply white backgrounds to autofilled inputs.
|
|
197
|
+
- **Not testing scrollbar.** Custom scrollbar may be invisible on dark backgrounds.
|
|
198
|
+
- **Storing theme in state instead of localStorage.** Causes flash of wrong theme on reload.
|
|
199
|
+
- **No `color-scheme: dark` declaration.** Browser UI elements (scrollbars, form controls) won't adapt without it.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Internationalization Visual Patterns Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
1. Logical Properties
|
|
5
|
+
2. RTL Layout Mirroring
|
|
6
|
+
3. Text Expansion by Language
|
|
7
|
+
4. CJK Text Rendering
|
|
8
|
+
5. Number and Currency Formatting
|
|
9
|
+
6. Font Stacks for Multi-Language
|
|
10
|
+
7. Icon Mirroring in RTL
|
|
11
|
+
8. Common Mistakes
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Logical Properties
|
|
16
|
+
|
|
17
|
+
Replace physical properties with logical ones. This makes RTL support automatic.
|
|
18
|
+
|
|
19
|
+
| Physical (avoid) | Logical (use) |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `margin-left` | `margin-inline-start` |
|
|
22
|
+
| `margin-right` | `margin-inline-end` |
|
|
23
|
+
| `padding-left` | `padding-inline-start` |
|
|
24
|
+
| `text-align: left` | `text-align: start` |
|
|
25
|
+
| `float: left` | `float: inline-start` |
|
|
26
|
+
| `border-left` | `border-inline-start` |
|
|
27
|
+
| `left: 0` | `inset-inline-start: 0` |
|
|
28
|
+
|
|
29
|
+
```css
|
|
30
|
+
/* Good: works in both LTR and RTL */
|
|
31
|
+
.sidebar { margin-inline-end: 2rem; padding-inline-start: 1rem; }
|
|
32
|
+
|
|
33
|
+
/* Bad: breaks in RTL */
|
|
34
|
+
.sidebar { margin-right: 2rem; padding-left: 1rem; }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. RTL Layout Mirroring
|
|
40
|
+
|
|
41
|
+
Set `dir="auto"` on user-generated content. Set `dir="rtl"` on the `<html>` element for RTL languages.
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<html lang="ar" dir="rtl">
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Flexbox and Grid automatically reverse in RTL when using logical properties. No extra CSS needed.
|
|
48
|
+
|
|
49
|
+
```css
|
|
50
|
+
/* This works in both directions automatically */
|
|
51
|
+
.nav { display: flex; gap: 1rem; }
|
|
52
|
+
.card { display: grid; grid-template-columns: auto 1fr; }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 3. Text Expansion by Language
|
|
58
|
+
|
|
59
|
+
English text expands significantly when translated. Design for the longest likely translation.
|
|
60
|
+
|
|
61
|
+
| Language | Expansion from English |
|
|
62
|
+
|---|---|
|
|
63
|
+
| German | +30-35% |
|
|
64
|
+
| French | +15-20% |
|
|
65
|
+
| Finnish | +30-40% |
|
|
66
|
+
| Russian | +15-25% |
|
|
67
|
+
| Chinese | -30-50% (shorter) |
|
|
68
|
+
| Japanese | -20-40% (shorter) |
|
|
69
|
+
| Arabic | +20-25% |
|
|
70
|
+
|
|
71
|
+
Rules:
|
|
72
|
+
- Never use fixed-width containers for translatable text.
|
|
73
|
+
- Buttons: use `min-width` not `width`. Allow text to wrap or grow.
|
|
74
|
+
- Navigation: test with German translations (longest common language).
|
|
75
|
+
- Truncate with `text-overflow: ellipsis` as a last resort, never as the design.
|
|
76
|
+
|
|
77
|
+
```css
|
|
78
|
+
/* Good: grows with content */
|
|
79
|
+
.btn { min-width: 120px; padding-inline: 1.5rem; white-space: nowrap; }
|
|
80
|
+
|
|
81
|
+
/* Bad: text overflows in German */
|
|
82
|
+
.btn { width: 120px; }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 4. CJK Text Rendering
|
|
88
|
+
|
|
89
|
+
Chinese, Japanese, and Korean text has different line-breaking and spacing rules.
|
|
90
|
+
|
|
91
|
+
```css
|
|
92
|
+
/* Allow CJK text to break at any character */
|
|
93
|
+
.cjk-text {
|
|
94
|
+
line-break: auto;
|
|
95
|
+
word-break: keep-all; /* Korean: don't break within words */
|
|
96
|
+
overflow-wrap: break-word;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* CJK doesn't need letter-spacing for readability */
|
|
100
|
+
:lang(zh), :lang(ja), :lang(ko) {
|
|
101
|
+
letter-spacing: 0;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
CJK text is denser — reduce line-height slightly:
|
|
106
|
+
```css
|
|
107
|
+
:lang(zh), :lang(ja) { line-height: 1.7; } /* vs 1.5 for Latin */
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 5. Number and Currency Formatting
|
|
113
|
+
|
|
114
|
+
Never hardcode currency symbols or number formats. Use `Intl.NumberFormat`.
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
// Automatic locale-aware formatting
|
|
118
|
+
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(1234.56)
|
|
119
|
+
// → "$1,234.56"
|
|
120
|
+
|
|
121
|
+
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(1234.56)
|
|
122
|
+
// → "1.234,56 €"
|
|
123
|
+
|
|
124
|
+
new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(1234)
|
|
125
|
+
// → "¥1,234"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For dates: use `Intl.DateTimeFormat`, never manual formatting.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 6. Font Stacks for Multi-Language
|
|
133
|
+
|
|
134
|
+
Include system fonts per script as fallbacks:
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
body {
|
|
138
|
+
font-family:
|
|
139
|
+
'Your Custom Font', /* Latin */
|
|
140
|
+
'Noto Sans SC', /* Simplified Chinese */
|
|
141
|
+
'Noto Sans JP', /* Japanese */
|
|
142
|
+
'Noto Sans KR', /* Korean */
|
|
143
|
+
'Noto Sans Arabic', /* Arabic */
|
|
144
|
+
system-ui, sans-serif; /* Fallback */
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Google's Noto family covers every Unicode script. Use it as the universal fallback.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 7. Icon Mirroring in RTL
|
|
153
|
+
|
|
154
|
+
Icons that imply direction MUST mirror in RTL. Icons that don't imply direction must NOT.
|
|
155
|
+
|
|
156
|
+
**Mirror in RTL:** arrows, back/forward, reply, undo/redo, text indent, send, search (if it implies reading direction), progress bars.
|
|
157
|
+
|
|
158
|
+
**Do NOT mirror:** play/pause, checkmarks, plus/minus, clock, globe, user, settings gear, download, external link.
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
[dir="rtl"] .icon-directional {
|
|
162
|
+
transform: scaleX(-1);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 8. Common Mistakes
|
|
169
|
+
|
|
170
|
+
- **Hardcoding `left`/`right` in CSS.** Use logical properties.
|
|
171
|
+
- **Fixed-width buttons.** They overflow in German/Finnish. Use `min-width`.
|
|
172
|
+
- **Testing only in English.** Design breaks with 35% longer strings.
|
|
173
|
+
- **`text-align: left` instead of `text-align: start`.** Breaks RTL.
|
|
174
|
+
- **Mirroring ALL icons in RTL.** Only directional icons should flip.
|
|
175
|
+
- **Hardcoding date formats.** `01/02/2026` means different dates in US vs UK. Use `Intl.DateTimeFormat`.
|
|
176
|
+
- **Not loading CJK fonts.** System fonts for CJK vary wildly. Include Noto Sans.
|
|
177
|
+
- **Ignoring `dir="auto"` on user content.** A Hebrew comment in an English page needs auto-detection.
|