Filled card
surface-container-highest · level0
Resting filled surface. Use when the card sits inside another container that needs a lift.
Material 3 기반의 한국어 우선 디자인 시스템. 타입 토큰, 색상 토큰, 컴포넌트 사양을 한 페이지에 모았습니다. 왼쪽 테마 스위처로 라이트/다크/시스템 자동 전환을 확인하세요.
38 system color roles per scheme. All values resolve via
var(--md-sys-color-*) tokens. Switch theme above to see
light, dark, or system-auto. The side-by-side preview on each
subsection forces both schemes simultaneously.
상단 테마 토글 (Light/Dark/Auto)에 따라 sys 토큰이 동적으로 변경됩니다.
15 type roles. Each row uses the matching .t-{role}
utility class — no literal sizes in markup. Samples include both
English and 한글 to verify mixed-script line height.
<button class="ax-button is-filled has-state-layer t-label-large">
Filled
</button>
Click to toggle. Text variant doesn't support toggle per spec.
<button class="ax-icon-button is-filled has-state-layer" aria-label="Add">
<span class="material-symbols-rounded notranslate" translate="no" aria-hidden="true" draggable="false">add</span>
</button>
Per M3 §6.4: outlined toggle should swap to a filled glyph when selected. Markup is identical — author updates the Material Symbols glyph name and selected state as needed.
Floating action button. 3 sizes (default 56, medium 80, large 96) × 6 color styles. Icon-only — for label use Extended FAB.
Validation specimen<button class="ax-fab" aria-label="Compose"><svg ...></svg></button>
<button class="ax-fab is-medium" aria-label="Compose">...</button>
<button class="ax-fab is-large" aria-label="Compose">...</button>
Tonal primary (default) / Tonal secondary / Tonal tertiary / Primary / Secondary / Tertiary. All visually equivalent in M3 §7.3.
FAB with text label + leading icon. 56px height, min-width 80px, label uses title-medium typography.
Validation specimen<button class="ax-fab-extended">
<svg class="ax-fab-extended__icon" ...></svg>
<span class="ax-fab-extended__label">Compose</span>
</button>
Composes .ax-button instances. Two variants: standard (selected button widens 15% per M3 §5.6) and connected (2px gap, full-pill outer corners, small inner corners). Two markup patterns supported — radio+label for single-select (CSS-only), button+aria-pressed for multi-toggle toolbars (JS).
Click any option — selected button widens with a spring transition. Pure CSS via :checked + .ax-button; native keyboard arrow-key navigation between options.
<fieldset class="ax-button-group">
<legend class="u-vh">View mode</legend>
<input type="radio" name="view" id="v-day"
class="ax-button-group__input">
<label for="v-day"
class="ax-button is-outlined has-state-layer">Day</label>
<input type="radio" name="view" id="v-week"
class="ax-button-group__input" checked>
<label for="v-week"
class="ax-button is-outlined has-state-layer">Week</label>
<input type="radio" name="view" id="v-month"
class="ax-button-group__input">
<label for="v-month"
class="ax-button is-outlined has-state-layer">Month</label>
</fieldset>
2px gap, full-pill outer corners, small inner corners. Inner corners morph to full pill when selected.
Each button toggles independently — wrong semantic for radio. Uses <button aria-pressed> with a small click handler (theme JS in production; inline demo here).
<div class="ax-button-group ax-button-group--connected"
role="toolbar" aria-label="Text formatting">
<button type="button" class="ax-button is-tonal has-state-layer"
aria-pressed="true">B</button>
<button type="button" class="ax-button is-tonal has-state-layer"
aria-pressed="false">I</button>
...
</div>
<script>
// Theme JS — toggle aria-pressed on click
document.querySelectorAll('[role="toolbar"] [aria-pressed]')
.forEach(btn => btn.addEventListener('click', () => {
const v = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!v));
}));
</script>
Composes two .ax-button instances — primary action + dropdown trigger. 2px gap, full-pill outer + 4dp inner (default M), morphs to 8dp on hover and 50% (full pill) when trailing button menu opens.
두 번째 split button (공유)은 aria-expanded="true" 상태 — trailing 모서리가 50% pill로 morph.
<div class="ax-split-button" role="group">
<button class="ax-button is-filled has-state-layer">저장</button>
<button class="ax-button is-filled has-state-layer"
aria-haspopup="menu" aria-expanded="false"
aria-label="더 많은 저장 옵션">
<svg class="ax-split-button__trailing-icon">...</svg>
</button>
</div>
Slot-based action surface. Two variants (docked full-width, edge-anchored / floating detached pill, level3) × two color schemes (standard surface-container / vibrant primary-container).
v0.3.5 scope: docked + floating, both color schemes. Slots accept icon-button / button / text-field / button-group. Floating-with-FAB variant deferred to v1.5+.
<div class="ax-toolbar ax-toolbar--floating ax-toolbar--vibrant"
role="toolbar">
<button class="ax-icon-button is-standard has-state-layer is-selected"
aria-pressed="true" aria-label="Like">...</button>
<button class="ax-icon-button is-standard has-state-layer"
aria-label="Comment">...</button>
...
</div>
surface-container-highest · level0
Resting filled surface. Use when the card sits inside another container that needs a lift.
surface-container-low · level1
Floats above the canvas. Hover state only matters when the card is interactive.
surface · 1px outline-variant
Defined by stroke, not fill. Best on busy backgrounds where elevation reads as noise.
<article class="card card--filled">
<h4 class="card__title t-title-large">Title</h4>
<p class="card__subtitle">Subtitle</p>
<p>Body</p>
</article>
aria-disabled="true"
Whole container fades to 0.38 opacity per §0.8 Pattern B.
아주 오래된 책들 사이에서 — full-width
A long, low corridor of shelves — inset
먼지 냄새 가득한 — middle inset
그날 오후의 정적.
<hr class="ax-divider is-style-inset" />
<header class="app-bar app-bar--small">
<span class="app-bar__leading">...</span>
<h1 class="app-bar__title t-title-large">Inbox</h1>
<span class="app-bar__trailing">...</span>
</header>
Demonstrates [data-scrolled="true"] — surface-container bg + level2 shadow.
<div class="tabs tabs--primary">
<button class="tabs__tab is-active">홈</button>
<button class="tabs__tab">탐색</button>
</div>
Refactored markup (v0.3.0+): outer <div class="text-field"> + visual <div class="text-field__container"> + optional <div class="text-field__bottom">. Slot grid: leading / prefix / input+label / suffix / trailing+error. Native :user-invalid drives error state without JS.
<div class="text-field text-field--filled">
<div class="text-field__container">
<input class="text-field__input" id="tf-email" placeholder=" " />
<label class="text-field__label" for="tf-email">Email</label>
</div>
<div class="text-field__bottom">
<span class="text-field__supporting">Helper text</span>
</div>
</div>
Prefix/suffix only appear when the label has floated up (focused or populated). Try entering letters in the price field — HTML5 pattern + :user-invalid auto-trigger error after blur.
<div class="text-field text-field--outlined">
<div class="text-field__container">
<input class="text-field__input" id="tf-price" placeholder=" "
inputmode="numeric" pattern="[0-9]*" maxlength="10" />
<label class="text-field__label" for="tf-price">가격</label>
<span class="text-field__prefix">₩</span>
<span class="text-field__suffix">KRW</span>
<span class="text-field__error-icon">...</span>
</div>
<div class="text-field__bottom">
<span class="text-field__supporting">숫자만 입력해 주세요.</span>
</div>
</div>
Leading icon (search affordance), trailing icon (clear action). Add .text-field--with-leading on the wrapper so the label clears the leading icon space. For interactive trailing buttons (clear, voice, dropdown), compose with .ax-icon-button.is-standard.has-state-layer. The clear button uses .is-clear for auto-hide-when-empty per M3 §32.
<div class="text-field text-field--outlined text-field--with-leading">
<div class="text-field__container">
<input class="text-field__input" id="tf-search" placeholder=" " />
<label class="text-field__label" for="tf-search">검색</label>
<span class="text-field__leading-icon"><svg .../></span>
<button class="ax-icon-button is-standard has-state-layer
text-field__trailing-icon is-clear"
aria-label="Clear">
<svg .../>
</button>
</div>
</div>
<label class="search-bar">
<span class="search-bar__leading-icon">...</span>
<input class="search-bar__input" placeholder="검색" />
<span class="search-bar__trailing">...</span>
</label>
18×18 box with 40×40 state layer halo. Selected primary fill + check icon. Indeterminate state shows horizontal bar. Pressed state shows preview-of-toggle color (M3 §13.3 quirk). Bucket E — system styles native <input type="checkbox"> via custom visual.
<label class="ax-checkbox">
<input type="checkbox" class="ax-checkbox__input" />
<span class="ax-checkbox__visual">
<svg class="ax-checkbox__check" viewBox="0 0 18 18" aria-hidden="true">
<polyline points="3.5,9.5 7.5,13.5 14.5,5.5"></polyline>
</svg>
</span>
<span class="ax-checkbox__label">Label</span>
</label>
20×20 outer ring + 10×10 inner dot. Same state layer + cross-color pressed quirk as checkbox. Used in radio groups for mutually-exclusive choices. Bucket E — styles native <input type="radio">.
<label class="ax-radio">
<input type="radio" name="theme" class="ax-radio__input" />
<span class="ax-radio__visual"></span>
<span class="ax-radio__label">Auto</span>
</label>
Track 52×32 pill with size-morphing handle (16 unselected → 24 selected → 28 pressed). Selected = primary fill, unselected = surface-container-highest with 2px outline. State layer 40×40 follows handle. Bucket E — styles native <input type="checkbox" role="switch">.
v0.2.5 scope: track + handle + state layer. Optional icon-on-handle deferred.
<label class="ax-switch">
<input type="checkbox" role="switch" class="ax-switch__input" />
<span class="ax-switch__track"></span>
<span class="ax-switch__label">알림 받기</span>
</label>
XS standard horizontal slider. Track 16px secondary-container, handle 4px primary (morphs to 2px on press). Uses native <input type="range"> with vendor pseudo-elements. Active fill via accent-color (browser-native) — for full M3 fidelity, author can add JS-driven gradient.
v0.2.5 scope: XS standard horizontal. S/M/L/XL sizes, vertical, centered, range, stops, value indicator deferred.
<div class="ax-slider">
<input type="range" class="ax-slider__input" min="0" max="100" value="40" aria-label="Volume" />
</div>
Visual structure only. Actual calendar logic (current month, navigation, validation) is the author's responsibility — typically driven by a JS library (flatpickr, vanilla-calendar) or server-rendered template. v0.4.0 = docked variant. Modal + modal-input variants reuse internals + dialog wrapper.
15일 = selected (primary fill), 11일 = range-start (primary fill, leading 모서리 둥글), 12-14일 = in-range band (secondary-container), 3일 = today (primary outline). 회색 날짜 = is-outside (38% opacity).
Date picker는 단독 사용보다 다음 3 패턴 권장 — Jetpack Compose DatePickerDocked / DatePickerModal / DatePickerModalInput와 동일:
.dialog로 wrap + 취소/확인 button (full calendar grid)실제 popup 위치/dismiss/날짜 계산은 author/JS 영역. 이 시스템은 visual primitives만 — calendar grid, date cell 상태 (today/selected/range/outside).
Visual structure only. Input variant uses real <input type="text" inputmode="numeric"> for typeable HH/MM, and <input type="radio"> + <label> for AM/PM (single-select, CSS-only). Clock dial drag interaction stays in author code.
시 (10) = 선택됨 (primary-container 채워짐). 분 (30) = 미선택. AM/PM = radio라 CSS-only로 클릭 토글, 키보드 화살표로 이동 가능. 사이에 1px outline 색 divider.
.is-24h modifier로 AM/PM selector 숨김. Compose is24Hour=true와 동일.
12-hour 라벨 + selector handle은 author/JS가 SVG 또는 absolute span으로 그림.
Date/Time picker는 단독으로 잘 안 씀. Compose가 정의한 3 패턴 중 선택:
.dialog로 wrap + 취소/확인 button (모바일에서 가장 흔한 패턴)실제 wiring (popup show/hide, dialog open/close)은 author/JS 영역. 이 시스템은 visual structure만 제공.
<button class="chip chip--filter has-state-layer" aria-pressed="false">
<span class="chip__label">Filter</span>
</button>
Input chip leading slot accepts a 24px avatar (size override on the leading element).
Status indicator. Two variants: small (6×6 dot) / large (numeric label, 16×16 to 16×34). Anchored top-trailing of host. Apply .has-badge to host for positioning context.
Used to mark unread / new / changed state without exact count.
<button class="ax-icon-button is-standard has-badge" aria-label="Notifications (unread)">
<svg ...></svg>
<span class="ax-badge"></span>
</button>
Counts up to ~3 chars. Use 99+ for overflow.
Use .is-inline to opt out of absolute positioning.
저장하지 않으면 마지막 편집 이후의 변경 사항이 모두 사라집니다.
Full-screen variant은 viewport 전체를 채우는 디자인 — 위 frame은 100% 컨테이너 fit으로 override되어 있습니다.
실제 viewport 크기로 띄우려면 위 버튼을 사용. body scroll이 잠깁니다.
<div class="snackbar">
<span class="snackbar__label">Saved</span>
<button class="snackbar__action">Undo</button>
</div>
Two variants: plain (short label, inverse-surface) / rich (subhead + body + actions, surface-container). Static .is-open for demo; positioning to a trigger is the author's job.
Short, attached to a trigger element. Single-line, max 240px.
<span class="ax-tooltip is-plain is-open" role="tooltip">새 글 쓰기</span>
Subhead + body + up to 2 actions. Dismissible.
<div class="ax-tooltip is-rich is-open" role="tooltip">
<span class="ax-tooltip__subhead">키보드 단축키</span>
<span class="ax-tooltip__supporting">⌘ + N으로 새 글 작성을 빠르게 시작합니다.</span>
<div class="ax-tooltip__actions">
<button class="ax-tooltip__action">자세히</button>
<button class="ax-tooltip__action">닫기</button>
</div>
</div>
Replaces indeterminate circular progress for short waits. Default (uncontained) / contained variants. Auto-respects prefers-reduced-motion.
<span class="ax-loading" role="status" aria-label="Loading">
<svg class="ax-loading__svg" viewBox="0 0 50 50" aria-hidden="true">
<circle class="ax-loading__circle" cx="25" cy="25" r="20"></circle>
</svg>
</span>
Pill container with primary-container fill, 38×48px.
Useful inside buttons or near labels.
Linear (4px track) + circular (40px). Determinate (set --_value via JS) or indeterminate (auto-animated). prefers-reduced-motion fallback for both. Wavy variant deferred to v1.5+.
Set --_value on the element to drive fill percentage.
<div class="ax-progress-linear is-determinate"
style="--_value: 60%;"
role="progressbar"
aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"></div>
<span class="ax-progress-circular is-indeterminate"
role="progressbar" aria-label="Loading">
<svg class="ax-progress-circular__svg" viewBox="0 0 40 40">
<circle class="ax-progress-circular__track" cx="20" cy="20" r="18"/>
<circle class="ax-progress-circular__active" cx="20" cy="20" r="18"/>
</svg>
</span>
<span class="ax-avatar is-size-lg"><span>지원</span></span>
Container 1+ list items. Single/two/three-line variants. Slots: leading (icon 24 / image 56), content (overline + label + supporting), trailing (icon 24 / text). Selection via .is-selected or aria-selected="true". Expressive shape state machine: rest → corner-extra-small, hover → corner-medium, focus/pressed/selected → corner-large.
<div class="ax-list" role="list">
<button class="ax-list__item" role="listitem">
<span class="ax-list__leading"><svg .../></span>
<span class="ax-list__content">
<span class="ax-list__label">홈</span>
</span>
<span class="ax-list__trailing-text">⌘1</span>
</button>
<hr class="ax-list__divider" />
</div>
Native horizontal scroll + scroll-snap. Item corner = corner-extra-large (28dp). Multi-browse default mixes large/medium/small items; hero variant features one large + small queue; uncontained bleeds past trailing edge for content-first browsing.
v0.3.5 scope: multi-browse, hero, uncontained. Center-aligned hero, multi-aspect, full-screen, item-morph animation deferred.
<div class="ax-carousel">
<div class="ax-carousel__track">
<div class="ax-carousel__item ax-carousel__item--large">
<div class="ax-carousel__item-media"><img .../></div>
<span class="ax-carousel__item-label">서울의 하루</span>
</div>
<div class="ax-carousel__item ax-carousel__item--medium">...</div>
<div class="ax-carousel__item ax-carousel__item--small">...</div>
</div>
</div>
No padding on trailing side — items extend off-screen. Drag/scroll to reveal.