💻

【Flutter】Variables機能でFigma×Flutterのデザイントークンを実現する

2023/09/27に公開

記事の概要

本記事では、Figma2023で登場した「Variables機能」を利用してデザイントークンを実現し、FigmaAPI経由でFlutterアプリに連携する方法について解説する。
FigmaからFlutterアプリへのVariablesの連携には、コマンド一つで連携可能なfigma_vars_to_dartというパッケージを利用する。
※本記事で解説する内容はこちらのプラン一覧に記載されているREST API for variablesの利用権限が必要です。現時点(2023/9/25)で、利用権限があるのはEnterpriseアカウントのみで、その他のアカウントではAPI経由でVariableを連携できないため注意してください。

前提知識

デザイントークンとは

色の値、スペース、タイポグラフィのスケールなどに名前をつけて定義し、デザインシステムを構成する最小要素をデザイントークンと呼ぶ。
デザイナー、エンジニア間の連携を効率化できる方法論として注目されている。
詳しい解説はこちらの記事などを参考にする。

Variables機能とは

Variables機能はFigma2023で登場し、デザイントークンの実現には不可欠なエイリアスの定義をすることができる。
Figmaではデザイン要素に対してkey-valueの定義はできたが、エイリアスの定義ができず、プラグイン等なしにデザイントークンの実現はできなかった。

FigmaAPIとは

Figma上の様々なデータを取得、利用することのできるAPI。利用方法はこちらを参考にする。本記事ではVariablesを取得するためにVariables REST APIを利用する。

開発環境

iphone14
iOS v16.1
Flutter v3.13.4

実装手順

1. FigmaでVariableを定義
Figmaにログインし、デザインページで何も選択していない状態で、Local Variablesからデザイントークンを作成する。


2. Figma APIのアクセストークンを生成
FigmaAPIを利用するために必要なアクセストークンを生成する。
アカウントのSettings>Personal access tokensからトークンを生成し、保存しておく。


作成時に設定するScopesの説明はこちらのページのAuthentication>Scopesを参照。
今回は、Variablesの権限が必要となる。

3. ライブラリのインストール

pubspec.yaml
dependencies:
  figma_vars_to_dart: ^0.0.4

4. FigmaからFlutterへVariablesを連携
ターミナルから以下のコマンドを実行することでFigma上で定義したVariablesがFlutterアプリへ連携され、ファイルが生成される。

dart pub run figma_vars_to_dart generate \
	 --token $FIGMA_TOKEN \ ※1
	 --fileId $YOUR_FIGMA_FILE_ID \ ※2
	 --dartOutputFolder lib/shared/ui_constants \
	 --jsonOutputFile vars.json

※1
$FIGMA_TOKEN :
手順2で生成したアクセトークンをセットする。
※2
$YOUR_FIGMA_FILE_ID :
ブラウザでFigmaを開いている場合はデザインページのURLのfile/以降が$YOUR_FIGMA_FILE_IDとなる。

デスクトップアプリで開いている場合は、タブを右クリックし「リンクをコピー」を押せばURLが取得できるため上記と同様にFILE_IDを取得し、セットする。

コマンド実行後、以下のファイルがFlutterアプリ内に生成される。

color_primitives.dart
color_primitives.dart
import 'package:flutter/widgets.dart';

class ColorPrimitives {
  final Color white;
  final Color pink;
  final Color green;
  final Color black;
  final Color blue;

  ColorPrimitives({
    required this.white,
    required this.pink,
    required this.green,
    required this.black,
    required this.blue,
  });

  factory ColorPrimitives.value() => ColorPrimitives(
        white: const Color(0xFFF2ECEC),
        pink: const Color(0xFFDD006A),
        green: const Color(0xFF8CC93E),
        black: const Color(0xFF2C2C2C),
        blue: const Color(0xFF3000F2),
      );
}
color_semantics.dart
color_semantics.dart
import 'package:flutter/widgets.dart';
import 'ui_constants.dart';

class ColorSemantics {
  final Color background;
  final Color buttonPrimary;

  ColorSemantics({
    required this.background,
    required this.buttonPrimary,
  });

  factory ColorSemantics.light(ColorPrimitives colorPrimitives) =>
      ColorSemantics(
        background: colorPrimitives.white,
        buttonPrimary: colorPrimitives.blue,
      );

  factory ColorSemantics.dark(ColorPrimitives colorPrimitives) =>
      ColorSemantics(
        background: colorPrimitives.black,
        buttonPrimary: colorPrimitives.green,
      );
}
ui_constants.dart
ui_constants.dart
export 'color_primitives.dart';
export 'color_semantics.dart';

4. FlutterでVariablesを利用

main.dart
import 'package:flutter/material.dart';
import 'shared/ui_constants/ui_constants.dart';
 
void main() {
  runApp(const MyApp());
}
 
// ①ライトモード/ダークモードで切り替え
extension ColorsExt on BuildContext {
  ColorSemantics get colors {
    final primitives = ColorPrimitives.value();
    if (MediaQuery.platformBrightnessOf(this) == Brightness.light) {
      return ColorSemantics.light(primitives);
    } else {
      return ColorSemantics.dark(primitives);
    }
  }
}
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
 
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});
 
  final String title;
 
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          // ②デザイントークンの利用
          backgroundColor: context.colors.background,
          // backgroundColor: context.colors.buttonPrimary,
          title: Text(
            title,
            style: TextStyle(color: Colors.black),
          ),
        ),
        body: null);
  }
}

動作確認

上記main.dartの「②デザイントークンの利用」backgroundColorでデザイントークンの利用を確認できる。
・backgroundColor: context.colors.backgroundの場合

・backgroundColor: context.colors.buttonPrimaryの場合

参考

figma_vars_to_dart
Figma plans and features
デザイントークンって何?
Guide to variables in Figma
Figma API

GitHubで編集を提案

Discussion