はじめに
こんにちは。ソリューションアーキテクトの宮口です。
builders.flash を読まれているデザイナーや開発者の方には Web サイトやアプリケーションのデザインに Figma を使われている方も多いのではないでしょうか。
Figma では公式で提供されている機能や豊富なプラグインで React コンポーネントの自動生成が柔軟に行えます。しかし、まだ開発現場では Bootstrap などの CSS フレームワークが使われることがあります。また、Figma では作成したデザインの CSS 部分をコピー & ペーストはできますが、ライブラリのように全てのコンポーネントをまとめて抽出するような機能は標準ではありません。
そこで本記事では、Figma API を用いて取得したデザインのスタイル情報から CSS ファイルを自動生成し、コンテンツ配信ネットワーク (CDN) 経由で配信するフローを AWS サービスを活用して開発する手順を紹介します。Figma デザインをソースとした CSS アセット化と公開を自動化し、アプリケーション開発を効率的に進める環境を作ることを目的としています。
ご注意
本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。
builders.flash メールメンバー登録
1. 今回開発するアプリケーションについて
本記事では、Figma のデザインを元に CSS ファイルを作成し、CDN で公開するアプリケーションを AWS Cloud Development Kit (AWS CDK) を用いて作成します。これを実現するために、以下の AWS サービスを利用しました。
Amazon S3 |
CSS ファイルを格納するストレージ。 |
Amazon CloudFront |
S3 に保存されている CSS ファイルを配信するための CDN サービス。 |
AWS Lambda |
Figma API を呼び出し CSS ファイルを作成、更に S3 に CSS ファイルをアップロードする関数。 |
Amazon EventBridge |
Lambda 関数を毎日 0 時に定期実行して、CSS ファイルを更新するためのスケジューラー。 |
AWS CDK |
上記の AWS サービスの設定をコード定義しデプロイ。 |
アーキテクチャ
これらの AWS サービスを用いて、最終的に以下のアーキテクチャを設計します。なお、今回構築したアプリケーションでは、使用したサービスごとに料金が発生するので、料金に関する詳細は 公式ページ をご参考ください。
それでは早速開発の流れを解説していきます。
アーキテクチャ図
2. Figma API を用いた CSS ファイルの作成
2-1. Figma 側での前準備
まずは、Figma API を用いて CSS ファイルを作成する手順です。今回はサンプルとして図のような簡単なログインフォームのデザインを Figma で用意しました。こちらのデザインを Figma API の 公式ドキュメント を参照して取得していきます。
まず API コールに必要なアクセストークンを 公式ドキュメント に従って取得します。続いてファイル ID が必要ですが、Figma のファイル URL に含まれています。具体的には、https://www.figma.com/file/xxx/ の文字列 xxx の部分です。
取得したアクセストークンとファイル ID は 3 章で AWS CDK の環境変数として利用するのでメモしておいてください。

2-2. CSS ファイル作成の実装
次に実際に API コールをしてみますが、利用するエンドポイントは GET file (GET https://www.figma.com/file/v1/files/<FILE_ID>) です。このエンドポイントのレスポンスは Figma ファイルに含まれるスタイル情報やメタデータを含んだほぼ全ての情報が JSON 形式で返されます。そのため、自力で取得したいノードのタイプやスタイルに応じて CSS コードに変換することが必要となります。
今回は簡易的に、ノードのタイプがコンポーネントのものだけを抽出して、スタイルや簡単なレイアウトを CSS コードとして生成しました。今回のケースですと、フォームとボタンの CSS コードを自動生成させます。
Python を用いて記述した例
以下のコードが Python を用いて記述した例です。figma2css.py というファイル名で作成しました。
import os
import json
import urllib3
class FigmaComponentExporter:
def __init__(self):
# FigmaのアクセストークンとファイルIDを取得
self.access_token = os.environ.get("FIGMA_ACCESS_TOKEN")
# Figma APIのURL
self.url = f"https://api.figma.com/v1/files/{os.environ.get('FIGMA_FILE_ID')}"
# CSSを保持する辞書
self.css_dict = {}
# 出力ファイル名を設定
self.output = "styles.css"
def _find_components(self, data):
# コンポーネントを探索する再帰関数
components = []
if isinstance(data, list):
for item in data:
components.extend(self._find_components(item))
elif isinstance(data, dict):
if data.get("type") == "COMPONENT" and data.get("children") is not None:
components.append(data)
for value in data.values():
components.extend(self._find_components(value))
return components
def _generate_css(self, class_name, style_dict):
# CSSを生成するメソッド
css = f".{class_name} {{\n"
for key, value in style_dict.items():
if value is not None:
css += f" {key}: {value};\n"
css += "}\n"
return css
def _rgba_to_hex(self, color):
# RGBAを16進数の色コードに変換するメソッド
hex_color = "#{:02X}{:02X}{:02X}".format(
int(color["r"] * 255), int(color["g"] * 255), int(color["b"] * 255)
)
return hex_color
def _generate_class_name(self, input_text):
# クラス名を生成するメソッド
class_name = "".join(c if c.isalnum() else "-" for c in input_text.lower())
class_name = "-".join(filter(None, class_name.split("-")))
return class_name
def _write_css_file(self):
# CSSファイルに書き込むメソッド
with open(self.output, "a") as f:
for class_name, style_dict in self.css_dict.items():
f.write(self._generate_css(class_name, style_dict))
def _output_component_css(self, component, parent_component_name=None):
# コンポーネントのCSSを出力するメソッド
class_name = (
self._generate_class_name(f"{parent_component_name}-{component['name']}")
if parent_component_name
else self._generate_class_name(component["name"])
)
self.css_dict[class_name] = {
"width": f"{int(component['absoluteBoundingBox']['width'])}px",
"height": f"{int(component['absoluteBoundingBox']['height'])}px",
"background": self._rgba_to_hex(component["background"][0]["color"])
if component.get("background")
else None,
"border-radius": f"{int(component.get('cornerRadius', 0))}px",
"border-width": f"{int(component.get('strokeWeight', 0))}px",
"border": f"solid {self._rgba_to_hex(component.get('strokes')[0].get('color'))}"
if component.get("strokes") and component.get("strokeWeight", 0) > 0
else None,
}
if component.get("children") is None:
return
for child in component["children"]:
if child["type"] == "TEXT":
self._output_text_css(child, parent_component_name, component["name"])
else:
self._output_component_css(child, parent_component_name=class_name)
def _output_text_css(self, child, parent_component_name, component_name):
# テキストのCSSを出力するメソッド
styles = child["style"]
style_dict = {
"font-family": f"'{styles['fontFamily']}'",
"font-size": f"{int(styles['fontSize'])}px",
"font-weight": styles["fontWeight"],
"color": self._rgba_to_hex(child["fills"][0]["color"]),
"line-height": f"{int(styles['lineHeightPercentFontSize'])}%",
"letter-spacing": f"{int(styles['letterSpacing'] / styles['fontSize'])}em",
}
class_name = (
self._generate_class_name(
f"{parent_component_name}-{component_name}-{child['name']}"
)
if parent_component_name
else self._generate_class_name(f"{component_name}-{child['name']}")
)
# CSSを辞書に追加
self.css_dict[class_name] = style_dict
def run(self):
# 出力ファイルが存在する場合は削除
if os.path.exists(self.output):
os.remove(self.output)
# Figma APIからデータを取得
http = urllib3.PoolManager()
response = http.request(
"GET", self.url, headers={"X-FIGMA-TOKEN": self.access_token}
)
if response.status == 200:
response = json.loads(response.data.decode("utf-8"))
else:
print(f"Failed to call API. Status code: {response.status}")
# コンポーネントセットを探索し、CSSを生成
components = self._find_components(response)
for component in components:
# コンポーネントごとにコメントを出力
with open(self.output, "a") as f:
f.write(f"/* {component['name']} */\n")
self.css_dict = {}
self._output_component_css(component)
# CSSファイルに書き込む
self._write_css_file()
if __name__ == "__main__":
exporter = FigmaComponentExporter()
exporter.run()
2-3. コードの動作確認
用意したコードをローカル環境で実行してみます。
python figma2css.py
styles.css
すると、下記のような CSS ファイル (styles.css) が自動生成されます。簡単なスタイル情報が取得できていることが確認できますね。
/* Button */
.button {
width: 100px;
height: 42px;
background: #0080DE;
border-radius: 10px;
border-width: 1px;
}
.button-label {
font-family: 'Noto Sans JP';
font-size: 18px;
font-weight: 400;
color: #FFFFFF;
line-height: 100%;
letter-spacing: 0em;
}
/* Form */
.form {
width: 400px;
height: 65px;
border-radius: 0px;
border-width: 1px;
}
.form-label {
font-family: 'Noto Sans JP';
font-size: 18px;
font-weight: 400;
color: #000000;
line-height: 100%;
letter-spacing: 0em;
}
.form-placeholder {
width: 400px;
height: 42px;
background: #FFFFFF;
border-radius: 8px;
border-width: 1px;
border: solid #000000;
}
3. AWS CDK を用いた自動生成・ファイル配信
3-1. AWS CDK プロジェクトの作成
CSS ファイルを自動生成するコードが完成したので、次は Amazon CloudFront + Amazon S3 を用いたファイル配信を行います。今回は利用する全ての AWS リソースの定義を AWS CDK (TypeScript) で記述していきたいと思います。
AWS CDK のツールキットがインストールされている前提で進めます。AWS CDK を利用できる環境をお持ちでない方は こちら のドキュメントに従って環境構築を行うことができます。
CDK プロジェクトを作成
まずは、以下のコマンドを実行して CDK プロジェクトを作成します。
mkdir figma2css
cd figma2css
npx cdk init app --language typescript
3-2. AWS CDK の実装
CDK プロジェクトの作成が完了したら、AWS CDK の開発を進めていきます。Figma API のアクセストークンとファイル ID をパラメータを記述する parameter.ts の作成と lib/figma2css-stack.ts を編集していきます。
まずは、parameter.ts というファイルを作成し、パラメータとして Figma API のアクセストークンとファイル ID を設定します。
「figmaAccessToken」と「figmaFileId」の情報を記述
以下のように、「figmaAccessToken」と「figmaFileId」の情報を記述します。これにより、CDK コードからこれらの値を変数として参照することができるようになります。
export interface AppParameter {
figmaAccessToken: string;
figmaFileId: string;
}
export const appParameter: AppParameter = {
figmaAccessToken: "FIGMA_ACCESS_TOKEN",
figmaFileId: "FIGMA_FILE_ID",
};
CDK のコードを記述
続いて、lib/figma2css-stack.ts に下記の CDK のコードを記述していきます。コード内には主に以下のようなリソース定義が記述されています。
CSS ファイルを格納するための S3 バケット
CSS ファイルを作成して、S3 バケットにアップロードする Lambda 関数
Lambda 関数を毎日 0 時に定期的に発火させる EventBridge
S3 オブジェクトをオリジンとして配信する CloudFront
lib/figma2css-stack.ts
3-3. Lambda 関数の実装
次に 2 章 で作成した Python コードを Lambda 関数として実行できる形式に変更した上で、S3 バケットにアップロードするように編集します。
Figma API の呼び出しに必要な情報や S3 バケット名は Lambda の環境変数から読み取るように修正を加えています。CDK のデプロイ時にコードとしてそのまま Lambda 関数にアップロードするために、src/figma2css.py に保存しておきます。以下が修正後のコードです。
src/figma2css.py
修正後のコード
import os
import json
import boto3
import urllib3
import botocore
class FigmaComponentExporter:
def __init__(self):
# Figmaのアクセストークンを取得
self.access_token = self._get_secret()
# Figma APIのURL
self.url = f"https://api.figma.com/v1/files/{os.environ.get('FIGMA_FILE_ID')}"
# CSSを保持する辞書
self.css_dict = {}
# 出力ファイル名を設定
self.tmp_output = "/tmp/styles.css"
def _get_secret(self):
# FigmaのアクセストークンをSecrets Managerから取得
boto3_session = boto3.session.Session()
boto3_client = boto3_session.client(service_name="secretsmanager")
secret_dict = boto3_client.get_secret_value(
SecretId=os.environ.get("SECRET_NAME")
)
access_token = secret_dict["SecretString"]
return access_token
def _find_components(self, data):
# コンポーネントを探索する再帰関数
components = []
if isinstance(data, list):
for item in data:
components.extend(self._find_components(item))
elif isinstance(data, dict):
if data.get("type") == "COMPONENT" and data.get("children") is not None:
components.append(data)
for value in data.values():
components.extend(self._find_components(value))
return components
def _generate_css(self, class_name, style_dict):
# CSSを生成するメソッド
css = f".{class_name} {{\n"
for key, value in style_dict.items():
if value is not None:
css += f" {key}: {value};\n"
css += "}\n"
return css
def _rgba_to_hex(self, color):
# RGBAを16進数の色コードに変換するメソッド
hex_color = "#{:02X}{:02X}{:02X}".format(
int(color["r"] * 255), int(color["g"] * 255), int(color["b"] * 255)
)
return hex_color
def _generate_class_name(self, input_text):
# クラス名を生成するメソッド
class_name = "".join(c if c.isalnum() else "-" for c in input_text.lower())
class_name = "-".join(filter(None, class_name.split("-")))
return class_name
def _write_css_file(self):
# CSSファイルに書き込むメソッド
with open(self.tmp_output, "a") as f:
for class_name, style_dict in self.css_dict.items():
f.write(self._generate_css(class_name, style_dict))
def _upload_s3_bucket(self):
# S3バケットにstyles.cssをアップロード
s3_bucket_name = os.environ.get("S3_BUCKET_NAME")
s3 = boto3.resource("s3")
try:
s3.Object(s3_bucket_name, "styles.css").put(
Body=open(self.tmp_output, "rb"), ContentType="text/css"
)
os.remove(self.tmp_output)
print("styles.css uploaded to S3 bucket")
except botocore.exceptions.ClientError as e:
print(f"Error uploading styles.css to S3 bucket: {e}")
def _output_component_css(self, component, parent_component_name=None):
# コンポーネントのCSSを出力するメソッド
class_name = (
self._generate_class_name(f"{parent_component_name}-{component['name']}")
if parent_component_name
else self._generate_class_name(component["name"])
)
self.css_dict[class_name] = {
"width": f"{int(component['absoluteBoundingBox']['width'])}px",
"height": f"{int(component['absoluteBoundingBox']['height'])}px",
"background": self._rgba_to_hex(component["background"][0]["color"])
if component.get("background")
else None,
"border-radius": f"{int(component.get('cornerRadius', 0))}px",
"border-width": f"{int(component.get('strokeWeight', 0))}px",
"border": f"solid {self._rgba_to_hex(component.get('strokes')[0].get('color'))}"
if component.get("strokes") and component.get("strokeWeight", 0) > 0
else None,
}
if component.get("children") is None:
return
for child in component["children"]:
if child["type"] == "TEXT":
self._output_text_css(child, parent_component_name, component["name"])
else:
self._output_component_css(child, parent_component_name=class_name)
def _output_text_css(self, child, parent_component_name, component_name):
# テキストのCSSを出力するメソッド
styles = child["style"]
style_dict = {
"font-family": f"'{styles['fontFamily']}'",
"font-size": f"{int(styles['fontSize'])}px",
"font-weight": styles["fontWeight"],
"color": self._rgba_to_hex(child["fills"][0]["color"]),
"line-height": f"{int(styles['lineHeightPercentFontSize'])}%",
"letter-spacing": f"{int(styles['letterSpacing'] / styles['fontSize'])}em",
}
class_name = (
self._generate_class_name(
f"{parent_component_name}-{component_name}-{child['name']}"
)
if parent_component_name
else self._generate_class_name(f"{component_name}-{child['name']}")
)
# CSSを辞書に追加
self.css_dict[class_name] = style_dict
def run(self):
# 出力ファイルが存在する場合は削除
if os.path.exists(self.tmp_output):
os.remove(self.tmp_output)
# Figma APIからデータを取得
http = urllib3.PoolManager()
response = http.request(
"GET", self.url, headers={"X-FIGMA-TOKEN": self.access_token}
)
if response.status == 200:
response = json.loads(response.data.decode("utf-8"))
else:
print(f"Failed to call API. Status code: {response.status}")
# コンポーネントセットを探索し、CSSを生成
components = self._find_components(response)
for component in components:
# コンポーネントごとにコメントを出力
with open(self.tmp_output, "a") as f:
f.write(f"/* {component['name']} */\n")
self.css_dict = {}
self._output_component_css(component)
# CSSファイルに書き込む
self._write_css_file()
self._upload_s3_bucket()
return {
"statusCode": 200,
"body": json.dumps("Figma component export completed successfully!"),
}
def lambda_handler(event, context):
exporter = FigmaComponentExporter()
result = exporter.run()
return result
3-4. アプリケーションのデプロイ
ここまで準備できると、あとは以下の CDK コマンドでデプロイするだけです。
npx cdk deploy
リソース確認
数分程度待つとデプロイが完了するので、作成したリソースを確認します。
AWS マネジメントコンソール から CloudFormation にアクセスします。作成された Figma2CssStack を選択し、出力タブを見ると CloudFrontURL という出力が確認できます。こちらの URL から CSS ファイルにアクセスすることができるので、コピーしておきます。
これで Figma API から取得したスタイル情報を CSS ファイルとして配信するためのフローを定義することができました。

4. サンプルの Web サイトを用いた動作確認
実際にうまく CSS ファイルを利用できるか確認してみましょう。CloudFront 経由で配信されている CSS ファイルを用いて簡単な HTML コードを記述したので、そちらを試しに確認してみます。5 行目に先ほどコピーした CloudFrontURL を記入します。
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Sample</title>
<link rel="stylesheet" href="https://xxx.cloudfront.net/styles.css" />
<style>
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
div {
margin-bottom: 35px;
}
</style>
</head>
<body>
<h1>ログイン</h1>
<div class="form">
<label class="form-label">メールアドレス</label>
<input class="form-placeholder" />
</div>
<div class="form">
<label class="form-label">パスワード</label>
<input class="form-placeholder" />
</div>
<button class="button">
<span class="button-label">送信</span>
</button>
</body>
</html>
Web サイトイメージ
正常に CSS スタイルが適用されていることが確認できました。左画像が CSS スタイル適用前、右画像が CSS スタイル適用後の Web サイトです。


AWS Amplify Studio の活用
余談ですが、今回のように CSS ファイルを利用するのではなく、React コンポーネントを利用される場合は AWS Amplify Studio の活用が有効です。先ほどと同じ Figma ファイルを AWS Amplify Studio に与えると、React コンポーネントを得ることができます。これを使い、以下のようなコードを記述することで、先程作成した Web サイトと同じデザインのものを作ることができます。
AWS Amplify Studio を用いた実装の詳細は AWS ブログ「AWS Amplify Studio – 最小限のプログラミングでFigmaからフルスタックのReactアプリを実現」をご参考ください。
React を使ったコード記述
コード
import React from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "@aws-amplify/ui-react";
import { Amplify } from "aws-amplify";
import awsconfig from "./aws-exports";
import "@aws-amplify/ui-react/styles.css";
import { studioTheme, Button, Form } from "./ui-components";
Amplify.configure(awsconfig);
const appStyle = {
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
};
const centeredStyle = {
display: "flex",
justifyContent: "center",
};
const verticalMargin = {
marginBottom: "35px",
};
const App = () => (
<div style={appStyle}>
<ThemeProvider theme={studioTheme}>
<h1 style={centeredStyle}>ログイン</h1>
<div style={verticalMargin} />
<Form />
<div style={verticalMargin} />
<Form />
<div style={verticalMargin} />
<div style={centeredStyle}>
<Button />
</div>
</ThemeProvider>
</div>
);
ReactDOM.render(<App />, document.getElementById("root"));
5. まとめ
最後まで閲覧いただきありがとうございました。今回は Figma ファイルを CSS ファイルに書き出し、アセットとして活用するための開発手順をお伝えました。
複雑な Figma コンポーネントやより詳細なスタイル・レイアウト情報を取得するには、Python コードを工夫する必要がありますが、Figma ファイルを CSS ファイルとしてアセット化する一連の流れをご理解いただけたのではないでしょうか。
ぜひ本記事の知見をご活用いただき、開発プロセスの効率化にお役立ていただけますと幸いです。
筆者プロフィール
宮口 直樹 (Naoki Miyaguchi)
アマゾン ウェブ サービス ジャパン合同会社 ソリューションアーキテクト
公共部門のお客様をご支援するソリューションアーキテクトとして活動しています。
プライベートでは、スペインや南米で人気スポーツのパデルにはまっています。
