JAL DESIGN.md
DESIGN.md
Design System Inspiration of JAL (日本航空)
Overview
Visual Theme & Atmosphere
JAL is Japan's flag carrier, and jal.co.jp is a booking surface — white, dense, and set
almost entirely in Noto Sans JP at 16px (188 of 232 text-bearing elements). One red,
#cc0000, carries the brand.
The token set underneath is a real design system with a component prefix (ds-button__,
ds-globalNav__) and 416 of the page's 479 custom properties are JAL's own. Its shape is
four semantic families, one per thing being coloured:
--surfaceColor-<role>-<state> what a fill is
--textColor-<role>-<state> what a label is
--iconColor-<role>-<state> what a glyph is
--strokeColor-<role>-<state> what a border or ring is
Two things make this reference unusual in the catalog. It publishes a font family per language — Japanese, English, Korean, Thai and three Chinese variants — and it publishes a colour for every frequent-flyer tier, from JMB Crystal to JGC Six Star.
And it is caught mid-migration: 87 of its 105 semantic colours exist twice, under two different naming conventions, with 18 of those pairs written in two different notations.
Primary tasks
- Search and book a flight; sign in to JMB.
- Reach the fare, status and service pages from a persistent global nav.
Brand Narrative
JAL's booking surface reads like infrastructure rather than marketing: one red, one typeface at one size, and a token set that spends its detail on the things an airline actually has to get right in software — a colour for every membership tier, a font for every market, and a state named for every way a control can be.
The migration visible in §14 fits that too. Nobody renames 87 colour tokens for fun; it is what a long-lived system looks like partway through being tidied.
Principles
- Four families, one per painted thing. Surface, text, icon, stroke.
- A font per language, not a fallback chain.
- The product's own vocabulary becomes tokens. Tier colours are part of the system.
Personas
Not researched. No persona claim is made from a UI capture.
Colors
Measured 2026-09-22 with getComputedStyle on jal.co.jp/ja-jp/.
The brand red, and its states
- Brand (
#cc0000) —--surfaceColor-primary-default,--textColor-primary-defaultand--strokeColor-focus-primaryall at once. The sign-in pill's fill, the nav's hover, the focus ring. - Brand Pressed (
#a30000) —--textColor-primary-pressed, and also--surfaceColor-danger-defaultand--strokeColor-form-error. Pressed red and danger red are the same value, which is worth knowing before reusing either. - Hover is not a separate hue but the same red at alpha:
--surfaceColor-primary-hoverisrgba(204,0,0,0.8)and--textColor-primary-hoverisrgba(204,0,0,0.6).
A red ramp beside it
--red-5 #fdf5f5 · --red-10 #fae5e5 · --red-20 #f7d6d6 ·
--red-60 #d63131 · --red-75 #b80000 · --red-80 #a30000, plus
--red-80-alpha10 and -alpha80. Six steps, and the brand's own #cc0000 is not one of
them — the ramp is for surfaces and the brand value sits outside it.
Ink, and the blues
- Foreground (
#000000) —--textColor-heading, also--neutral-black. - Subtle (
#666666) —--textColor-subtle,--iconColor-default,--neutral-60. - Disabled (
#999999) —--textColor-disabled,--neutral-40. - Neutral 80 (
#333333) —--iconColor-black-pressed,--neutral-80. - Neutral 3 (
#f5f6f6) — the lightest surface step. - Informative (
#2a344b) —--textColor-informative-default, and the same value is--accent-prussianBlue. A navy that does the job "this is a link or a notice". - Informative Pressed (
#4c559b) —--iconColor-link-defaultand--strokeColor-focus-link; a brighter indigo for the pressed and link states. - Icon White Pressed (
#e1e3e6) — the one icon value that is neither black nor white.
Surfaces and lines
Canvas (#ffffff) · Border (#d5d5d5) — --strokeColor-outline-default,
--strokeColor-divider-content and --surfaceColor-*-disabled share it ·
Surface Muted (#e2e7ed) — --surfaceColor-informative-muted, also
--accent-powderBlue · Surface Alert (#fdf5f5) · Surface Emphasis (#e8e8e8) —
--surfaceColor-base-light-emphasis, also --neutral-10.
The accent family — named after colours, not roles
Accent Steel Blue (#6a7181) · Accent Gray Blue (#b7c1cd) ·
Accent Light Cyan (#f0f7ff) · Prussian Blue #2a344b · Powder Blue #e2e7ed, each with
alpha80 and some with alpha60 variants. These are the one group named by hue rather
than by role — the rest of the system never says what a colour looks like, only what it is for.
Membership tiers as tokens
This is the part no other reference here has.
| JMB Status | JGC / LSP Status | ||
|---|---|---|---|
| Crystal Red | #ba1334 | Three Star | #960a20 |
| Sapphire Blue | #066fbf | Four Star | #112a6c |
| Emblem Gold | #edc900 | Five Star | #535a60 |
| Emblem Bronze | #733b11 | Six Star | #000000 |
| Blue | #0f327b | JMB Elite | #652d73 |
| Green | #097048 | ||
| Diamond Black | #000000 |
Eleven distinct values for loyalty status, published as design tokens on the public booking page. A consumer building anything JAL-adjacent gets the tier palette for free; a consumer building something else should not take them, because each one means a specific membership level.
What is not JAL's
The page also loads a shadcn/Tailwind layer: 31 properties in oklch (--background,
--card, --border, --popover, --muted, --sidebar-*, --chart-*) plus 63 --tw-*.
This matters more than usual, because the most-rendered text colour on the page is
oklch(0.145 0 0) on 150 elements — and that is shadcn's default foreground, not JAL's.
--textColor-heading #000000 is what the system declares; the shadcn default is what much of
the page actually paints. Neither the oklch values nor rgb(31, 41, 55) (Tailwind's gray-800,
4 elements) are recorded here.
Typography
Font family — one per language
--fontFamily-japanese "Noto Sans JP", sans-serif
--fontFamily-english "Noto Sans", sans-serif
--fontFamily-korean "Noto Sans KR", sans-serif
--fontFamily-thai "Noto Sans Thai", sans-serif
--fontFamily-chineseSimplified "Noto Sans SC", sans-serif
--fontFamily-chineseTraditional "Noto Sans TC", sans-serif
--fontFamily-chineseHongKong "Noto Sans HK", sans-serif
--fontFamily-serif serif
--fontFamily-poppins poppins, sans-serif
Seven scripts, each an explicit token rather than a fallback chain — an airline selling in
every market it flies to, with the language switch as a token lookup. Only Noto Sans JP
rendered on the measured surface (232 of 232 elements).
--fontFamily-poppins is declared and did not render here. The webfonts the browser actually
loaded were Noto Sans JP, AdobeClean-Regular and AdobeClean-Bold — Adobe Clean arriving
from an embedded Adobe component rather than from a JAL token, so it is not recorded.
Two type scales, both published
Semantic, 12 tokens: --text-caption-sm 11 · -caption-md 12 · -caption-lg 14 ·
-body-sm 12 · -body-md 14 · -body-lg 16 · -body-xl 18 · -heading-xs 16 ·
-heading-sm 18 · -label-xs 12 · -label-sm 14 · -label-lg 18.
Numeric, 11 tokens in rem: --fontSize-11 0.6875rem through --fontSize-40 2.5rem.
The two overlap from 11 to 18 and only the numeric one goes above it. Rendered, the page is almost monotone: 16px on 188 elements, then 18px on 14 and 14px on 11. Weights are 400 on 154 and 700 on 78 — two weights, no middle.
Layout
- Colour by what is being painted. Surface, text, icon and stroke are four parallel
families, so
--iconColor-subtle-hoverexists separately from--textColor-subtle. - States live in the token name, not in the consumer's code:
default,hover,pressed,disabled,selected, and compound ones likebase-selected-hoverandbase-hover-compact. - Alpha does the hover work.
rgba(204,0,0,0.8)rather than a second red.
Responsive Behavior
Not measured. Desktop 1440×1100 only.
Elevation & Depth
No shadow was observed on either declared control, and no shadow token appears among the
416. Separation is #d5d5d5 lines and the #e8e8e8 / #f5f6f6 surface steps.
Motion & Easing
No motion token exists among the 416, and no transition value was captured on either declared control.
Tier 1 sources: https://www.jal.co.jp/ja-jp/ (live booking surface — 479 custom properties read via getComputedStyle, of which 416 are JAL's own and 63 are --tw-*; two controls measured at rest, hover and focus, 2026-09-22); https://www.jal.com/ja-jp/ (JAL's corporate surface — a second brand-owned Japanese surface rendering the same #cc0000 on 13 elements and the same #2a344b informative navy, on a separate stylesheet carrying only two custom properties; cited for ownership and for the brand red, not for the token set, 2026-09-22)
Components
Measured with focus read for every control before the mouse moved at all.
Sign-in pill
#cc0000 fill, #ffffff label, 1px solid #cc0000, 9999px radius, 193×34, 14px/700.
Hover changes nothing — :hover matched true while the reading was taken.
Focus draws outline: #cc0000 solid 2px.
solid is what makes that ring JAL's: the browser's own is auto 1px. And the value is
declared — --strokeColor-focus-primary is #cc0000. Token and render agree.
Global nav item
#ffffff, #000000 label, no border, square, 80×47, 16px/700.
Hover turns the label #cc0000 and grows a 3px bottom border in the same red.
Focus does all of that and adds outline: #cc0000 solid 1px with a 2px radius — so the
keyboard state is the hover state plus a ring, rather than a different state.
Two controls measured and not declared
A transparent 254×48 pill (#cc0000 text and border) and a filled 234×48 pill (#a30000) both
sit below the fold. :hover matched false on two separate attempts, and neither reported
:focus-visible. Their rest values are real and consistent with the two above; their states
were not measured, so they are not declared.
Radius
Rendered: 9999px on 17 elements — the pill is the house shape — then 0px 0px 8px 8px
on 6 (dropdown panels), 16px on 4, 8px on 4, 4px on 3 and 50px on 2.
States, and a system caught mid-rename
Both declared components carry measured hover and focus. The pill does nothing on hover and rings on focus; the nav item colours on hover and rings on top of that. Pressed was not measured on either, so no pressed value is recorded.
The state vocabulary is much larger than what was observed: default, hover, pressed,
disabled, selected, plus base-selected-hover, base-hover-compact,
base-dark-emphasis, base-light-emphasis, disabled-light and disabled-dark. Recorded
here in prose; only the two observed states reached the component block.
87 colours published twice
The 105 --surfaceColor / textColor / iconColor / strokeColor tokens are also published
under a second convention, --color-<kebab-case>:
--surfaceColor-informative-muted #e2e7ed
--color-surface-color-informative-muted #e2e7ed ← same value, second name
69 pairs are byte-identical. 18 more are the same colour in a different notation:
--iconColor-white-hover rgba(255, 255, 255, 0.6)
--color-icon-color-white-hover #fff9 ← 0x99 = 153 = 0.6 × 255
--surfaceColor-base-hover-compact rgba(0, 0, 0, 0.1019607843)
--color-surface-color-base-hover-compact #0000001a ← 0x1a = 26 = 0.102 × 255
18 have no twin at all. The same doubling appears in the other scales: --space-16 beside
--spacing-space-16, and a semantic type scale beside a numeric one.
No conflict is recorded, because there is none — every pair that exists agrees. It is one system written down twice, and a consumer should take the camelCase names, which are the complete set.
Do's and Don'ts
Do
- Pick the family by what you are colouring — a glyph takes
iconColor, nottextColor. - Use
#cc0000atsolid 2pxfor focus; it is declared as--strokeColor-focus-primaryand measured on the rendered pill. - Take hover as the base colour at 0.8 or 0.6 alpha, which is what the system does.
Don't
- Don't reuse the JMB and JGC tier colours decoratively.
#066fbfmeans Sapphire. - Don't read
oklch(0.145 0 0)as JAL's ink even though it paints 150 elements — it is shadcn's default. JAL's heading colour is#000000. - Don't assume
#a30000is only the pressed red; it is also danger and form-error.
Voice & Tone
Not assessed as copy. 2,810 characters on the measured surface, most of it fare and route names.
Agent Prompt Guide
Quick Color Reference
#cc0000 brand · #a30000 pressed and danger · #000000 heading · #666666 subtle ·
#999999 disabled · #2a344b informative · #4c559b link · #d5d5d5 lines ·
#e2e7ed muted surface · #fdf5f5 alert · #e8e8e8 emphasis · #f5f6f6 lightest ·
#333333 icon pressed · #e1e3e6 icon on dark · accents #6a7181 #b7c1cd #f0f7ff ·
reds #fae5e5 #f7d6d6 #d63131 #b80000
Example Component Prompts
- "A 34px sign-in pill:
#cc0000fill and border,#ffffff14px/700 label, fully rounded, no hover change, focusoutline: #cc0000 solid 2px." - "A 47px nav item:
#00000016px/700 on white; hover turns the label#cc0000and adds a 3px#cc0000bottom border."
색상
브랜드
면과 글자
상태
그 밖의 색
단계별 색
red10–75
타이포그래피
- Noto Sans JP산세리프 · 오픈 글꼴 · OFLGoogle Fonts에서 설치
- Noto Sansenglish · 오픈 글꼴 · OFLGoogle Fonts에서 설치
- Noto Sans KRkorean · 오픈 글꼴 · OFLGoogle Fonts에서 설치
- Noto Sans Thaithai · 오픈 글꼴 · OFLGoogle Fonts에서 설치
- Noto Sans SCchinese-simplified · 오픈 글꼴 · OFLGoogle Fonts에서 설치
- Noto Sans TCchinese-traditional · 오픈 글꼴 · OFLGoogle Fonts에서 설치
- Noto Sans HKchinese-hongkong
타입 스케일
- display-lg--fontSize-40; the largest published step40px
- display--fontSize-3636px
- display-sm--fontSize-3232px
- heading-lg--fontSize-2828px
- heading--fontSize-2424px
- title--fontSize-2020px
- lead--text-body-xl, -heading-sm and -label-lg18px
- base--text-body-lg and -heading-xs; 188 of 232 rendered text elements16px
여백과 모양
여백
- s44px
- s88px
- s1616px
- s2020px
- s2424px
- s3232px
- s4040px
- s4848px
- s6464px
- s9696px
모서리 반경
- xs2px
- sm4px
- md8px
- lg16px
- pill9999px
컴포넌트
- button-primarybutton-primary채움 #cc0000 · 글자 #ffffff · 테두리 1px solid #cc0000 · 반경 9999px · 높이 34px · 글자 크기·굵기 14px / 700
- 호버 #cc0000
- 포커스 #cc0000
- nav-linknav-link채움 #ffffff · 글자 #000000 · 반경 0px · 높이 47px · 글자 크기·굵기 16px / 700
- 호버 #cc0000
- 포커스 #cc0000