開いているFigma DesignファイルのローカルスタイルをCSVにまとめます。カラー、テキスト、エフェクト、レイアウトグリッドを一覧化し、名前や定義を確認するためのプラグインです。ライブラリ全体やVariablesの書き出しとは対象が異なります。
出力する情報と対象外の情報
| 種別 | 出力内容 |
|---|---|
| 共通 | 種別・スタイルID・名前・説明・キー |
| カラー | 塗りの種類、単色のHEX/RGBA、グラデーションの停止点など |
| テキスト | フォント、サイズ、行高、字間、段落、装飾など |
| エフェクト | 影の位置・ぼかし・色、背景/レイヤーぼかしなど |
| グリッド | パターン、サイズ、列や行の設定など |
CSVは18列です。元記事の20列の部分的な例と、完全な18列コードの食い違いを整理しました。塗りや効果は確認用の文字列表現で、CSSや復元用のデータ形式ではありません。PostScript名は取得結果にあれば記録し、空欄になる場合があります。
getLocalPaintStylesAsyncなど4種類の非同期APIでローカル定義を取得します。チームライブラリのスタイルすべてを取得するAPIではありません。出典:ローカルスタイルAPI。
作成してCSVを保存する
- 以下の3ファイルを保存し、デスクトップアプリでmanifestを読み込みます。
- スタイルを調べたいファイルで起動します。
- 取得が終わったらCSVを保存を押します。
- ブラウザーまたはアプリの保存先を確認し、表計算ソフトで読み込みます。
文字コードはBOM付きUTF-8です。CSVのカンマ・引用符・改行を処理します。式と解釈されやすい先頭文字にはアポストロフィーを付けるため、その値は元の文字列と完全に同じではありません。元値の厳密な移行には別のデータ形式を使い、CSVは一覧確認に使ってください。
ファイルを作成する
FigmaのNew pluginで生成したmanifestを基に、次の設定を使います。掲載例ではidを省略しています。自分のmanifestに割り当てられたidがある場合はその値を保ち、サンプルの任意文字列に置き換えません。3ファイルを同じフォルダへ保存してください。
manifest.json
{
"name": "スタイルCSV書き出し",
"api": "1.0.0",
"main": "code.js",
"ui": "ui.html",
"editorType": [
"figma"
],
"documentAccess": "dynamic-page",
"networkAccess": {
"allowedDomains": [
"none"
]
}
}code.js
// スタイルCSV書き出し - 開いているファイルのローカルスタイルをCSVにまとめて保存
// ---------------------------------------------------------
// ヘッダーを日本語化
// ---------------------------------------------------------
figma.showUI(__html__, { width: 360, height: 120 });
// --- CSVエスケープ ---
function csvEscape(v) {
if (v === null || v === undefined) return "";
let s = String(v);
if (/^[=+@\-\t\r\n]/.test(s)) s = "'" + s;
if (/[",\r\n]/.test(s)) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
}
// --- 色変換ユーティリティ ---
function to255(n) {
return Math.max(0, Math.min(255, Math.round(n * 255)));
}
function hex2(n) {
return n.toString(16).padStart(2, "0").toUpperCase();
}
function rgbToHex(r, g, b) {
return "#" + hex2(to255(r)) + hex2(to255(g)) + hex2(to255(b));
}
function rgbaToCss(r, g, b, a) {
return (
"rgba(" +
to255(r) +
", " +
to255(g) +
", " +
to255(b) +
", " +
a.toFixed(3) +
")"
);
}
// --- Paint配列を文字列化 ---
function serializePaints(paints) {
return paints
.map(function (p) {
switch (p.type) {
case "SOLID": {
var opacity =
p.opacity !== undefined && p.opacity !== null ? p.opacity : 1;
var hex = rgbToHex(p.color.r, p.color.g, p.color.b);
var rgba = rgbaToCss(p.color.r, p.color.g, p.color.b, opacity);
return "単色(" + hex + " | " + rgba + ")";
}
case "GRADIENT_LINEAR":
case "GRADIENT_RADIAL":
case "GRADIENT_ANGULAR":
case "GRADIENT_DIAMOND": {
var kind = p.type.replace("GRADIENT_", "グラデーション_");
var stops = p.gradientStops
.map(function (s) {
var hex = rgbToHex(s.color.r, s.color.g, s.color.b);
var a =
s.color.a !== undefined && s.color.a !== null ? s.color.a : 1;
var pct = Math.round((s.position || 0) * 100);
return pct + "% " + hex + " (a:" + a.toFixed(3) + ")";
})
.join(" - ");
return kind + "(" + stops + ")";
}
case "IMAGE":
return "画像塗り(scale:" + p.scaleMode + ")";
default:
return p.type || "UNKNOWN";
}
})
.join(" / ");
}
// --- TextStyle補助 ---
function serializeLineHeight(v) {
if (!v || v === "AUTO") return "自動";
if (v.unit === "PIXELS") return v.value + "px";
if (v.unit === "PERCENT") return v.value + "%";
return "自動";
}
function serializeLetterSpacing(v) {
if (!v || v === "AUTO") return "自動";
if (v.unit === "PIXELS") return v.value + "px";
if (v.unit === "PERCENT") return v.value + "%";
return "自動";
}
function serializeTextCase(v) {
return v || "";
}
function serializeTextDecoration(v) {
return v || "";
}
// --- Effect配列を文字列化 ---
function serializeEffects(effects) {
return effects
.map(function (e) {
switch (e.type) {
case "DROP_SHADOW":
case "INNER_SHADOW": {
var c = e.color;
var hex = rgbToHex(c.r, c.g, c.b);
var spread = "spread" in e && e.spread != null ? e.spread : 0;
return (
e.type +
"(x:" +
e.offset.x +
", y:" +
e.offset.y +
", blur:" +
e.radius +
", spread:" +
spread +
", color:" +
hex +
", a:" +
c.a.toFixed(3) +
")"
);
}
case "LAYER_BLUR":
case "BACKGROUND_BLUR":
return e.type + "(半径:" + e.radius + ")";
default:
return e.type || "UNKNOWN";
}
})
.join(" / ");
}
// --- LayoutGrid配列を文字列化 ---
function serializeLayoutGrids(grids) {
return grids
.map(function (g) {
if (g.pattern === "GRID") {
return (
"グリッド(サイズ:" +
g.sectionSize +
", 色:" +
rgbToHex(g.color.r, g.color.g, g.color.b) +
", a:" +
g.color.a.toFixed(3) +
")"
);
}
var base = g.pattern + "/" + g.alignment;
return (
base +
"(カウント:" +
(g.count != null ? g.count : "自動") +
", サイズ:" +
g.sectionSize +
", ガター:" +
g.gutterSize +
", オフセット:" +
g.offset +
")"
);
})
.join(" / ");
}
// --- スタイル抽出 ---
async function rowsFromColorStyles() {
return (await figma.getLocalPaintStylesAsync()).map(function (s) {
return {
styleType: "カラー",
styleId: s.id,
name: s.name,
description: s.description || "",
key: s.key || "",
paints: serializePaints(s.paints),
};
});
}
async function rowsFromTextStyles() {
return (await figma.getLocalTextStylesAsync()).map(function (s) {
var f = s.fontName;
var hasFont = f !== figma.mixed;
return {
styleType: "テキスト",
styleId: s.id,
name: s.name,
description: s.description || "",
key: s.key || "",
fontFamily: hasFont ? f.family : "",
fontStyle: hasFont ? f.style : "",
fontPostScriptName: s.fontPostScriptName || "",
fontSize: s.fontSize !== figma.mixed ? String(s.fontSize) : "",
lineHeight: serializeLineHeight(s.lineHeight),
letterSpacing: serializeLetterSpacing(s.letterSpacing),
paragraphSpacing:
s.paragraphSpacing !== figma.mixed ? String(s.paragraphSpacing) : "",
paragraphIndent:
s.paragraphIndent !== figma.mixed ? String(s.paragraphIndent) : "",
textCase: s.textCase !== figma.mixed ? serializeTextCase(s.textCase) : "",
textDecoration:
s.textDecoration !== figma.mixed
? serializeTextDecoration(s.textDecoration)
: "",
};
});
}
async function rowsFromEffectStyles() {
return (await figma.getLocalEffectStylesAsync()).map(function (s) {
return {
styleType: "エフェクト",
styleId: s.id,
name: s.name,
description: s.description || "",
key: s.key || "",
effects: serializeEffects(s.effects),
};
});
}
async function rowsFromGridStyles() {
return (await figma.getLocalGridStylesAsync()).map(function (s) {
return {
styleType: "レイアウトグリッド",
styleId: s.id,
name: s.name,
description: s.description || "",
key: s.key || "",
layoutGrids: serializeLayoutGrids(s.layoutGrids),
};
});
}
// --- CSV組み立て(日本語ヘッダー) ---
function toCSV(rows) {
var header = [
"スタイル種別",
"スタイルID",
"名前",
"説明",
"キー",
"塗り",
"フォントファミリー",
"フォントスタイル",
"PostScript名",
"文字サイズ",
"行の高さ",
"字間",
"段落間隔",
"段落インデント",
"テキスト変換",
"装飾",
"エフェクト",
"レイアウトグリッド",
];
var lines = [];
lines.push(header.map(csvEscape).join(","));
rows.forEach(function (r) {
var values = [
r.styleType,
r.styleId,
r.name,
r.description,
r.key,
r.paints || "",
r.fontFamily || "",
r.fontStyle || "",
r.fontPostScriptName || "",
r.fontSize || "",
r.lineHeight || "",
r.letterSpacing || "",
r.paragraphSpacing || "",
r.paragraphIndent || "",
r.textCase || "",
r.textDecoration || "",
r.effects || "",
r.layoutGrids || "",
];
lines.push(values.map(csvEscape).join(","));
});
return lines.join("\n");
}
figma.ui.onmessage = async function (msg) {
if (!msg) return;
if (msg.type === "ready") {
try {
const groups = await Promise.all([rowsFromColorStyles(), rowsFromTextStyles(), rowsFromEffectStyles(), rowsFromGridStyles()]);
const rows = [].concat(...groups);
figma.ui.postMessage({ type: "save-csv", filename: "styles_" + Date.now() + ".csv", content: toCSV(rows) });
} catch (_) { figma.ui.postMessage({ type: "error", message: "スタイルの取得に失敗しました。" }); }
} else if (msg.type === "done") figma.closePlugin();
};ui.html
<!doctype html><html lang="ja"><meta charset="utf-8"><p id="status">スタイルを取得中…</p>
<button id="save" disabled>CSVを保存</button><button id="close">閉じる</button>
<script>
let csv = null;
const status = document.getElementById("status"), save = document.getElementById("save");
window.onmessage = event => {
if (event.source !== parent) return;
const msg = event.data && event.data.pluginMessage;
if (!msg) return;
if (msg.type === "save-csv") { csv = msg; save.disabled = false; status.textContent = "CSVを保存できます。"; }
if (msg.type === "error") status.textContent = msg.message;
};
save.onclick = () => {
if (!csv) return;
const url = URL.createObjectURL(new Blob(["\uFEFF", csv.content], { type: "text/csv;charset=utf-8" }));
const a = document.createElement("a"); a.href = url; a.download = csv.filename;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
status.textContent = "保存を要求しました。ダウンロード先を確認してください。";
};
document.getElementById("close").onclick = () => parent.postMessage({ pluginMessage: { type: "done" } }, "*");
parent.postMessage({ pluginMessage: { type: "ready" } }, "*");
</script></html>空欄や色の値を確認するとき
ローカルスタイルがない場合もヘッダーは出力されます。外部ライブラリだけを使うファイルでは、この一覧が少なくても取得漏れとは限りません。グラデーションや画像塗りを単色のHEXと同じものとして扱わず、塗り種別を確認します。
ライブラリ参照の検査は外部スタイルの点検、Variablesの出力はVariablesとJSONを参照してください。
元記事の操作例
以下は元記事で掲載していた開発時の画面です。今回のコード改稿後に撮影した検証画像ではありません。現行の表示や変更結果は、複製したファイルで確認してください。




検証範囲と関連ガイド
今回確認したのは掲載コードの構文と、該当する入力・例外処理のローカル模擬検証です。Figmaデスクトップアプリ上での動作確認とは区別しています。改稿後のコードと、元記事が参照していたGitHub上の版が一致するとは限りません。
開発環境と登録手順はFigmaプラグイン開発入門、公開はCommunityへの申請を参照してください。
