Slide 1

Slide 1 text

そのデザイントークン、 Flutterでも同じ見た目になりますか? FlutterKaigi 2026 Day1 株式会社アサイン 中水優輝 この発表のスライドはこちら 01

Slide 2

Slide 2 text

自己紹介 中水 優輝 Yuki Nakamizu 株式会社アサイン / Webフロントエンジニア デザインシステムの構築を担当 Webフロントエンド歴 7年目 2ヶ月目 Flutter歴 X この発表のスライドはこちら 02

Slide 3

Slide 3 text

今日の話 Webとアプリで 同じデザイントークンを使っているのに、 見た目がそろわないところがあった話 ©ASSIGN Inc. All Rights Reserved. 03

Slide 4

Slide 4 text

WebとFlutterへ配布するデザイントークン 背景 › 課題 › 解決策 › 結果 › まとめ WebとFlutterのアプリで見た目をそろえるため、同じデザイントークンを配布している デザインシステム アプリ Web向けのスタイルとlint import Flutter向けのスタイルとlint import デザイントークン ©ASSIGN Inc. All Rights Reserved. 04

Slide 5

Slide 5 text

デザイントークンとは 背景 › 課題 › 解決策 › 結果 › まとめ デザイントークンは、UIの細かい見た目を決める、デザインの最小単位 デザイントークン Aa 部品 色 タイポグラフィ 余白 角丸 影 グラデーション 線 アニメーション ©ASSIGN Inc. All Rights Reserved. 画面 05

Slide 6

Slide 6 text

トークンの種類 背景 › 課題 › 解決策 › 結果 › まとめ 配布しているデザイントークンは、色、タイポグラフィ、余白、角丸の4種類 色 ©ASSIGN Inc. All Rights Reserved. Aa タイポグラフィ 余白 角丸 06

Slide 7

Slide 7 text

トークンの種類 背景 › 課題 › 解決策 › 結果 › まとめ 色、余白、角丸と違い、タイポグラフィは見た目がそろわなかった 色 ©ASSIGN Inc. All Rights Reserved. Aa タイポグラフィ 余白 角丸 07

Slide 8

Slide 8 text

Webとアプリの見比べ 背景 › 課題 › 解決策 › 結果 › まとめ 同じデザイントークンを使っても、Webとアプリでテキストの詰まり方が違う Web アプリ https://xxx.jp ©ASSIGN Inc. All Rights Reserved. 08

Slide 9

Slide 9 text

文字間の書き方 背景 › 課題 › 解決策 › 結果 › まとめ ほかの3つと違い、文字間はFlutterでは比率で指定できない フォントサイズ font-size: 20px; fontSize: 20 太さ font-weight: 600; fontWeight: FontWeight.w600 行間 line-height: 1.5; height: 1.5 文字間 letter-spacing: 0.05em; letterSpacing: ? ©ASSIGN Inc. All Rights Reserved. 09

Slide 10

Slide 10 text

Flutterの文字間 背景 › 課題 › 解決策 › 結果 › まとめ Flutterの文字間は、ピクセルの値でしか指定できない /// The amount of space (in logical pixels) to add between each letter. final double? letterSpacing; 出典:Flutter SDK 3.38.2 packages/flutter/lib/src/painting/text_style.dart ©ASSIGN Inc. All Rights Reserved. 10

Slide 11

Slide 11 text

比率をそのまま渡した場合 背景 › 課題 › 解決策 › 結果 › まとめ フォントサイズの5%のつもりで0.05を渡しても、文字間は0.05ピクセルになる letterSpacing: 1.0 letterSpacing: 0.05 ©ASSIGN Inc. All Rights Reserved. 11

Slide 12

Slide 12 text

Webとの違い 背景 › 課題 › 解決策 › 結果 › まとめ 比率で書けないので、フォントサイズごとに長さへ直す必要がある 1つ書けば、ブラウザが計算する CSS letter-spacing: 0.05em; ©ASSIGN Inc. All Rights Reserved. サイズごとに、自分で計算して書く Dart TextStyle(fontSize: 16, letterSpacing: 0.8) TextStyle(fontSize: 20, letterSpacing: 1.0) 12

Slide 13

Slide 13 text

換算の関数 背景 › 課題 › 解決策 › 結果 › まとめ トークンは比率のままWebと共有し、Flutterへ渡す直前にピクセルへ換算する Web letter-spacing: 0.05em; デザイントークン 0.05(フォントサイズの5%) Flutterへ渡す直前に換算 換算の関数 文字間 = 比率 × フォントサイズ static double px(double em, double fontSize) => em * fontSize; Flutter letterSpacing: 1.0 フォントサイズ20pxのとき ©ASSIGN Inc. All Rights Reserved. 13

Slide 14

Slide 14 text

テキストスタイルの関数 背景 › 課題 › 解決策 › 結果 › まとめ テキストスタイルごとに関数を用意し、その中でフォントサイズと文字間を一緒に決める abstract final class DsTextStyles { static TextStyle display20W6({…}) { … } static TextStyle display24W6({…}) { … } … static TextStyle textNormal14W4({…}) { … } static TextStyle textNormal16W6({…}) { … } … } ©ASSIGN Inc. All Rights Reserved. 14

Slide 15

Slide 15 text

文字間が決まる流れ 背景 › 課題 › 解決策 › 結果 › まとめ 20と0.05が関数に渡り、計算されて、文字間は1ピクセルになる 1. フォントサイズが20の関数 static TextStyle display20W6({double Function(double value)? scale}) { final fontSize = (scale ?? _spScale)(DsFontSize.s20); return TextStyle( fontSize: fontSize, fontWeight: DsFontWeight.semibold, letterSpacing: DsLetterSpacing.px(DsLetterSpacing.normalEm, fontSize), height: DsLineHeight.normal, … ); } ©ASSIGN Inc. All Rights Reserved. 15

Slide 16

Slide 16 text

文字間が決まる流れ 背景 › 課題 › 解決策 › 結果 › まとめ 20と0.05が関数に渡り、計算されて、文字間は1ピクセルになる 2. fontSizeに20、normalEmに0.05が入る static TextStyle display20W6({double Function(double value)? scale}) { final fontSize = (scale ?? _spScale)(DsFontSize.s20); return TextStyle( fontSize: 20, fontWeight: DsFontWeight.semibold, letterSpacing: DsLetterSpacing.px(0.05, 20), height: DsLineHeight.normal, … ); } ©ASSIGN Inc. All Rights Reserved. 16

Slide 17

Slide 17 text

文字間が決まる流れ 背景 › 課題 › 解決策 › 結果 › まとめ 20と0.05が関数に渡り、計算されて、文字間は1ピクセルになる 3. 関数で計算される static TextStyle display20W6({double Function(double value)? scale}) { final fontSize = (scale ?? _spScale)(DsFontSize.s20); return TextStyle( fontSize: 20, fontWeight: DsFontWeight.semibold, letterSpacing: 1.0, height: DsLineHeight.normal, … ); } ©ASSIGN Inc. All Rights Reserved. 17

Slide 18

Slide 18 text

Lintルール 背景 › 課題 › 解決策 › 結果 › まとめ TextStyleにフォントサイズや文字間を数字で書くと、Lintルールが警告する name_label.dart return const Text( '私の名前は中水優輝です', style: TextStyle(fontSize: 20, letterSpacing: 0.05), ); ターミナル $ dart run custom_lint lib/name_label.dart:10:34 • Typography値を直書きしないでください。Typographyトークンを使用してください。 • avoid_raw_typography • WARNING lib/name_label.dart:10:53 • Typography値を直書きしないでください。Typographyトークンを使用してください。 • avoid_raw_typography • WARNING ©ASSIGN Inc. All Rights Reserved. 18

Slide 19

Slide 19 text

Webとアプリの見比べ 背景 › 課題 › 解決策 › 結果 › まとめ Webとアプリで、同じテキストスタイルを選ぶだけで文字の見た目を統一できた Web 記事のタイトル https://xxx.jp ©ASSIGN Inc. All Rights Reserved. アプリ style: DsTextStyles.textNormal16W6(), 19

Slide 20

Slide 20 text

Webとアプリの見比べ 背景 › 課題 › 解決策 › 結果 › まとめ Webとアプリで、同じテキストスタイルを選ぶだけで文字の見た目を統一できた Web 読了の目安 https://xxx.jp ©ASSIGN Inc. All Rights Reserved. アプリ style: DsTextStyles.textNormal14W4(), 20

Slide 21

Slide 21 text

まとめ 背景 › 課題 › 解決策 › 結果 › まとめ トークンでは意味を共有し、Flutter向けの変換はデザインシステムに集約する 共有するのは意味 デザイントークン 意味:フォントサイズの5% ©ASSIGN Inc. All Rights Reserved. 変換はここに集約 デザインシステム 比率 × フォントサイズで換算 Lintで換算の漏れを防ぐ Web 0.05em のまま書ける Flutter ピクセルで受け取る 21

Slide 22

Slide 22 text

文字間以外の違い 背景 › 課題 › 解決策 › 結果 › まとめ 文字間のほかにも、Webと違うところがあった 項目 カラーコード #FF9000CC 透明度が最後 Color(0xCCFF9000) 余白 padding: 16px; Padding(padding: …, child: Text(…)) 影のぼかし box-shadow: 0 0 10px BoxShadow(blurRadius: 10) ©ASSIGN Inc. All Rights Reserved. 要素自身に書く ぼかしの標準偏差 5 透明度が最初 Textには余白の引数がなく、外から包む ぼかしの標準偏差 約6.27 22

Slide 23

Slide 23 text

おわりに 背景 › 課題 › 解決策 › 結果 › まとめ Webとの違いを楽しみながら、 これからもFlutterを学習していく ©ASSIGN Inc. All Rights Reserved. 23