Amazonで商品を見るセール会場へ

FigmaのスタイルをCSVに書き出すプラグイン|色・文字・効果・グリッド

開いているFigma DesignファイルのローカルスタイルをCSVにまとめます。カラー、テキスト、エフェクト、レイアウトグリッドを一覧化し、名前や定義を確認するためのプラグインです。ライブラリ全体やVariablesの書き出しとは対象が異なります。

目次

出力する情報と対象外の情報

種別出力内容
共通種別・スタイルID・名前・説明・キー
カラー塗りの種類、単色のHEX/RGBA、グラデーションの停止点など
テキストフォント、サイズ、行高、字間、段落、装飾など
エフェクト影の位置・ぼかし・色、背景/レイヤーぼかしなど
グリッドパターン、サイズ、列や行の設定など

CSVは18列です。元記事の20列の部分的な例と、完全な18列コードの食い違いを整理しました。塗りや効果は確認用の文字列表現で、CSSや復元用のデータ形式ではありません。PostScript名は取得結果にあれば記録し、空欄になる場合があります。

getLocalPaintStylesAsyncなど4種類の非同期APIでローカル定義を取得します。チームライブラリのスタイルすべてを取得するAPIではありません。出典:ローカルスタイルAPI。

作成してCSVを保存する

  1. 以下の3ファイルを保存し、デスクトップアプリでmanifestを読み込みます。
  2. スタイルを調べたいファイルで起動します。
  3. 取得が終わったらCSVを保存を押します。
  4. ブラウザーまたはアプリの保存先を確認し、表計算ソフトで読み込みます。

文字コードは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を参照してください。

元記事の操作例

以下は元記事で掲載していた開発時の画面です。今回のコード改稿後に撮影した検証画像ではありません。現行の表示や変更結果は、複製したファイルで確認してください。

デザインファイルで定義されているスタイル情報をCSV形式で書き出すプラグインの概要を示す画面。
2025年09月の元記事の掲載例。改稿後コードの検証画像ではありません。
出力されたCSVファイルをスプレッドシートや表計算ソフトに読み込んで確認した一覧画面。
2025年09月の元記事の掲載例。改稿後コードの検証画像ではありません。
プラグインの作成に必要なファイル構成の一覧。
2025年09月の元記事の掲載例。改稿後コードの検証画像ではありません。
デスクトップ版Figmaで読み込んだプラグインを実行する操作画面。
2025年09月の元記事の掲載例。改稿後コードの検証画像ではありません。

検証範囲と関連ガイド

今回確認したのは掲載コードの構文と、該当する入力・例外処理のローカル模擬検証です。Figmaデスクトップアプリ上での動作確認とは区別しています。改稿後のコードと、元記事が参照していたGitHub上の版が一致するとは限りません。

開発環境と登録手順はFigmaプラグイン開発入門、公開はCommunityへの申請を参照してください。

サービス

Service

デザイン制作に関心がありましたら、ぜひ詳細をご覧ください。

次に学ぶ・作業環境を選ぶ

学習を続けたい方や、作業環境を整えたい方は、目的に合うガイドをご覧ください。

Figmaのおすすめ書籍

Figma用PCの選び方

周辺機器の優先度

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次