Skip to content

feat: Phase 1 MD3 token migration for all components - #47

Closed
rdlabo wants to merge 25 commits into
mainfrom
feature/md3-tokens-phase0
Closed

rdlabo wants to merge 25 commits into
mainfrom
feature/md3-tokens-phase0

Conversation

@rdlabo

@rdlabo rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Introduces the Material Design 3 token foundation in src/styles/default-variables.scss.
  • Migrates all 20 component SCSS files to consume --md-sys-* tokens.
  • Adds MD3 button variants (button-elevated, button-tonal) and shape option (button-square).
  • Adds MD3 top app bar variants (toolbar-small, toolbar-medium, toolbar-large).
  • Adds MD3 card variants (card-elevated, card-outlined).
  • Tokenizes colors, shapes, elevations and motion for Button, Toolbar, Card, Chip, FAB, List, Range, Searchbar, Segment, Toggle, Tabs, Content, DateTime and overlays (Alert, Action Sheet, Loading, Modal, Popover, Toast).

Design principles

  • No !important.
  • CSS custom properties preferred over Shadow Parts.
  • Legacy --token-* aliases kept in default-variables.scss for backward compatibility.
  • All selectors respect the .md3-disabled opt-out and the mode="md" boundary.

Test plan

  • npm run build passes
  • npm run lint passes
  • Visual regression review on the PR (mac/linux pixel diffs expected)

Generated with Devin


Devin Review

rdlabo and others added 7 commits September 16, 2026 11:11
Establish the 3-layer token architecture for the MD3 theme:

1. Source colors (--md-sys-source-*) map to the Ionic palette and
   are overridable for Material You / dynamic color.
2. MD3 color roles (--md-sys-color-*) form the public API.
3. Components will consume roles, never raw Ionic variables.

Also add shape, typescale, motion, state and elevation tokens,
plus dark-palette overrides. color-mix() is used as a CSS-only
fallback adapter, not a canonical tonal palette generator.

Legacy --token-elevation-* and --token-expressive-* are kept as
aliases for backwards compatibility.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ion-button:
- Map Ionic fills to MD3 variants: solid -> filled, outline -> outlined,
  clear -> text, plus new button-elevated and button-tonal.
- Use --md-sys-color-* for fills, state layers and typography.
- Apply --md-sys-shape-corner-full by default, --md-sys-shape-corner-small
  for button-square.
- Keep small / large sizing and icon-only sizing from the existing theme.

ion-toolbar:
- Surface background / on-surface text via --md-sys-color-*.
- Add opt-in toolbar-small / toolbar-medium / toolbar-large variants.
- Preserve existing insets and icon-only button back-fill behavior.

No !important used; all changes rely on CSS custom properties and
selectors that respect the md3-disabled opt-out.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- ion-card: default to M3 "filled" (surface-container-highest), add
  card-elevated (surface-container-low + elevation-1) and card-outlined
  (surface + outline-variant border) variants.
- ion-chip: surface-container-low background, on-surface text and M3
  state layer opacities.
- ion-fab: primary-container / on-primary-container, M3 shape and
  elevation tokens.

All changes go through CSS custom properties and avoid !important.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- ion-searchbar: surface-container-highest input with on-surface / on-
  surface-variant colors and extra-large corner radius.
- ion-segment: outlined segmented button group with secondary-container
  selection, shared borders and pill-shaped first/last buttons.
- ion-tabs: bottom navigation bar on surface-container, 80px height,
  active icon on a secondary-container pill.
- ion-toggle: M3 switch using surface-container-highest track, primary
  checked track, outline handle and on-primary checked handle.

All changes go through CSS custom properties.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- ion-list: use md-sys outline-variant for divider color, extra-small
  shape tokens and standard motion easing for floating labels.
- ion-range: apply M3 slider color roles (surface-container-highest
  track, primary active track, on-primary thumb, inverse-surface pin)
  and motion tokens while preserving the existing 4dp bar / halo
  shape logic.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…dal, Popover, Toast)

Apply M3 surface, shape, elevation and color roles to the overlay
components while keeping Ionic layout and safe-area behavior.

- Alert: surface-container-highest with extra-large radius, primary /
  destructive button colors.
- Action Sheet: surface-container-low with bottom extra-large radius,
  destructive option color.
- Loading: surface-container-highest wrapper.
- Modal: level-3 shadow and extra-large radius for desktop / sheet.
- Popover: surface-container with medium radius.
- Toast: inverse-surface / inverse-on-surface snackbar with
  inverse-primary action color and extra-small corners.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- ion-content: surface / on-surface.
- ion-datetime: surface-container-highest background and on-surface title.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

Devin Review

Comment on lines +15 to +16
--background: var(--md-sys-color-primary);
--color: var(--md-sys-color-on-primary);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Ionic の色指定が MD3 の固定色に置き換わる

color 属性付きボタンにも primary 背景と文字色が常に適用されます。同じ固定指定を持つ toolbar、card、chip、content、FAB、toggle でも指定色が表示されません。

Learn more

Ionic は color 属性から .ion-color--ion-color-base / --ion-color-contrast を設定します。今回、文書側のコンポーネント規則が --background--color を直接指定するため、その公開 API より MD3 の固定ロールが優先されます。デモにも color="success" の button と toggle、color="primary" の toolbar と card など、影響を受ける既存利用があります。

Example: <ion-button color="success" fill="solid">Save</ion-button> は success 色になるべきですが、常に --md-sys-color-primary--md-sys-color-on-primary で描画されます。

Recommended fix: 各コンポーネントで .ion-color の場合は --ion-color-base / --ion-color-contrast を使う上書きを追加してください。button の outline/clear、toggle の checked 状態、toolbar、card、chip、content、FAB など、固定ロールを設定した全コンポーネントを同じ方針で監査してください。

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/styles/components/ion-segment.scss Outdated
Comment on lines +25 to +31
&:first-of-type {
--border-radius: var(--md-sys-shape-corner-full) 0 0 var(--md-sys-shape-corner-full);
}

&:last-of-type {
--border-radius: 0 var(--md-sys-shape-corner-full) var(--md-sys-shape-corner-full) 0;
--border-width: 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 RTL のセグメント外枠が逆側で丸くなる

RTL では先頭ボタンが右端へ移動しても、左角だけが丸められます。外角が四角くなり、内側の継ぎ目が丸く表示されます。

Learn more

first-of-typelast-of-type は DOM 順を参照しますが、セグメントの視覚順は RTL で反転します。現在の border-radius は物理的な左・右を固定しているため、DOM 上の先頭要素に左側の角丸を付け続けます。

Example: DOM 順が A、B、C の場合、RTL では A が右端、C が左端です。現在は A の左角と C の右角が丸くなり、どちらもグループ内側に現れます。

Recommended fix: :dir(rtl) で first/last の半径を反転するか、論理方向に対応する方法で開始側・終了側の角丸を指定してください。区切り線の辺も RTL で外周と重複しないよう同時に確認してください。

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

// switches to dark mode. These overrides adapt the derived Material roles
// for dark surfaces while preserving Ionic colors as the theme source.

html.ion-palette-dark {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 system・always ダークモードでダーク用トークンが適用されない

ダーク補正は html.ion-palette-dark だけに限定されています。dark.system.cssdark.always.css では、MD3 コンテナ色がライト用の混合率のまま残ります。

Learn more

Ionic は class 方式以外に、常時適用する dark.always.css と OS 設定に従う dark.system.css を提供しています。リポジトリのデモ設定にも両方式が選択肢として残っていますが、ここでは class 方式のセレクターだけを検出します。その結果、基礎の Ionic 変数は暗色へ変わっても、surface-container などは 2–8% のライト用比率を使い続けます。

Example: dark.system.css を読み込み、OS をダークへ切り替えると --ion-background-color は暗くなります。しかし --md-sys-color-surface-container-highest は 8% のままで、期待するダーク用 20% へ変わりません。

Recommended fix: class、always、system の各公式方式に対応するスコープで同じダークロールを定義してください。system 方式には対応する prefers-color-scheme 条件を使い、Ionic の適用条件と同期させてください。

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +305 to +307
--md-sys-color-inverse-surface: var(--ion-color-dark, #e6e0e9);
--md-sys-color-inverse-on-surface: var(--ion-color-dark-contrast, #1d1b20);
--md-sys-color-inverse-primary: var(--ion-color-primary-tint, #d0bcff);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 ダークモードの inverse ロールが反転しない

ダークパレットでも --ion-color-dark は暗色のため、inverse surface は暗いままです。toast はライト時と同じ暗色背景になり、inverse 配色へ反転しません。

Learn more

CSS var() のフォールバックは変数が未定義の場合だけ使われます。Ionic は --ion-color-dark--ion-color-dark-contrast を通常定義するため、ここに書かれた明色・暗色のフォールバックは選ばれません。

Example: ダークパレットで --ion-color-dark: #222428、その contrast が白なら、toast は暗色背景と白文字のままです。期待される inverse surface は明色背景と暗色文字です。

Recommended fix: ダークスコープでは明色パレットの base/contrast を inverse surface/on-surface に割り当ててください。カスタム Ionic パレットでも対になる色を維持できることを確認してください。

Suggested change
--md-sys-color-inverse-surface: var(--ion-color-dark, #e6e0e9);
--md-sys-color-inverse-on-surface: var(--ion-color-dark-contrast, #1d1b20);
--md-sys-color-inverse-primary: var(--ion-color-primary-tint, #d0bcff);
--md-sys-color-inverse-surface: var(--ion-color-light, #e6e0e9);
--md-sys-color-inverse-on-surface: var(--ion-color-light-contrast, #1d1b20);
--md-sys-color-inverse-primary: var(--ion-color-primary-tint, #d0bcff);
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/styles/default-variables.scss Outdated
Comment on lines +275 to +277
--token-elevation-6: var(--md-sys-elevation-level-4);
--token-elevation-8: var(--md-sys-elevation-level-5);
--token-elevation-12: var(--md-sys-elevation-level-5);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 旧 elevation alias は従来値を保持していない

後方互換用の 6・8・12 alias は、以前より低い影レベルへ解決されます。既存利用箇所の見た目を維持する必要があるか確認してください。

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

📊 Ionic 9 Playwright Test Report

View the detailed Ionic 9 report: https://rdlabo-dev.github.io/ionic-theme-md3/pr-47/

Ionic 8 runs against the same screenshots in a separate matrix job. View both results in the workflow run.

Devin Review feedback on PR #47:

- Add `.ion-color` overrides to Button, Card, Chip, FAB, Toggle and
  Toolbar so the Ionic `color` attribute (via `--ion-color-base` /
  `--ion-color-contrast`) is honored on top of MD3 tokens.
- Fix `ion-card` setting the `color` property directly, which would
  override the Ionic `.ion-color` rule.
- Restore the original legacy `--token-elevation-8` and
  `--token-elevation-12` values; keep 0/1/3 mapped to M3 levels and
  6 mapped to M3 level 5.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Playwright test results

failed  6 failed
passed  76 passed

Details

stats  82 tests across 2 suites
duration  2 minutes, 13 seconds
commit  5a8426f
info  This detailed result covers Ionic 9 only. Ionic 8 runs against the same screenshots in a separate matrix job; check the workflow run for both results. To update the screenshots, comment with /update-screenshots.

Failed tests

chromium › screenshot.spec.ts › Screenshot Tests - All Routes › should match screenshot for action-sheet:all
chromium › screenshot.spec.ts › Screenshot Tests - All Routes › should match screenshot for action-sheet:button-only
chromium › screenshot.spec.ts › Screenshot Tests - All Routes › should match screenshot for action-sheet:no-cancel
chromium › screenshot.spec.ts › Screenshot Tests - Dark Mode › should match dark mode screenshot for action-sheet:all
chromium › screenshot.spec.ts › Screenshot Tests - Dark Mode › should match dark mode screenshot for action-sheet:button-only
chromium › screenshot.spec.ts › Screenshot Tests - Dark Mode › should match dark mode screenshot for action-sheet:no-cancel

github-actions Bot added a commit that referenced this pull request Sep 16, 2026
github-actions Bot added a commit that referenced this pull request Sep 16, 2026
@rdlabo
rdlabo force-pushed the feature/md3-tokens-phase0 branch from 9a50498 to 91d8809 Compare September 16, 2026 03:11
rdlabo and others added 2 commits September 16, 2026 12:16
Restore the iOS/Ionic button shape (pill, circular icon-only, square
variant) while keeping MD3 color roles and typography.

- Add the :not(.button-round) selector back so toolbar rules do not
  override --border-radius.
- Use 999px / 12px directly instead of MD3 shape tokens.
- Revert icon sizes to the iOS defaults (24/20/24).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Use --ion-color-light for the item border so the inset list group
keeps its iOS card-like shape without a dark MD3 outline between
items. Color (surface background, notes) remains MD3.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
github-actions Bot added a commit that referenced this pull request Sep 16, 2026
iOS is essentially the default light implementation, so keep
ion-content background as the platform default rather than forcing
--md-sys-color-surface.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
github-actions Bot added a commit that referenced this pull request Sep 16, 2026
github-actions Bot added a commit that referenced this pull request Sep 16, 2026
github-actions Bot added a commit that referenced this pull request Sep 16, 2026
- Restore Ionic/main shape values (border-radius, height, spacing, handle radius, etc.) for action-sheet, alert, card, chip, fab, list, loading, modal, popover, range, searchbar, segment, tabs, toast, toggle, toolbar.
- Keep MD3 color, typography, elevation, and state tokens where they only affect color.
- Remove non-Ionic MD3-only variants (button-elevated/tonal, card-elevated/outlined, toolbar size variants) so defaults stay predictable.
- Keep Ionic color="..." and md3-disabled behavior intact.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
github-actions Bot added a commit that referenced this pull request Sep 16, 2026
@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

@github-actions

Copy link
Copy Markdown
Contributor

✅ Screenshots have been updated successfully!

The new screenshots have been committed to this PR.

Android の ion-list[inset] では divider が背景色と同一なので、
--border-color を item background と同じ値にした。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
github-actions Bot added a commit that referenced this pull request Sep 16, 2026
Ionic のデフォルト形状(bar-height/knob サイズ等)を維持しつつ、
色トークンだけ MD3 を当てるようにした。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ Failed to update screenshots.

Please check the workflow run for details.

@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

@github-actions

Copy link
Copy Markdown
Contributor

❌ Failed to update screenshots.

Please check the workflow run for details.

ion-range を Ionic 標準形状に戻したため、rtl.spec.ts の
固定ピクセル値 / カスタム transform 期待値を標準形状に合わせた。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

@github-actions

Copy link
Copy Markdown
Contributor

❌ Failed to update screenshots.

Please check the workflow run for details.

github-actions Bot added a commit that referenced this pull request Sep 16, 2026
getComputedStyle はパーセーセンテージを px に解決するため、
fixture width 200px に対して 50% = 100px を期待するようにした。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

github-actions Bot added a commit that referenced this pull request Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Screenshots have been updated successfully!

The new screenshots have been committed to this PR.

…ples

- iOS27 と同じく role: 'preferred' に対応する MD3 スタイルを追加
  - alert: filled primary pill
  - action-sheet: filled primary row
- destructive は既存の error 色を維持
- demo に preferred / destructive が両方表示されるようにボタンを更新

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

@github-actions

Copy link
Copy Markdown
Contributor

✅ Screenshots have been updated successfully!

The new screenshots have been committed to this PR.

github-actions Bot added a commit that referenced this pull request Sep 16, 2026
.alert-wrapper の focus outline が黒い縁取りとして見えていた。
MD3 ダイアログにそのような縁取りは存在しないため outline: none を追加。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

github-actions Bot added a commit that referenced this pull request Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Screenshots have been updated successfully!

The new screenshots have been committed to this PR.

- destructive / preferred のアイコンが --color を見ており、
  テキスト色 (--button-color) だけ変更しても icon に反映されていなかった
- --button-color と --color の両方を設定し、アイコンも含めて統一

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

github-actions Bot added a commit that referenced this pull request Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Screenshots have been updated successfully!

The new screenshots have been committed to this PR.

@rdlabo

rdlabo commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/update-screenshots

@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ No screenshot changes detected.

The current screenshots are already up to date.

@rdlabo rdlabo closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant