Marp(スライド生成)ナレッジ
Marp(Markdown Presentation Ecosystem)を使ったスライド生成に関する学びを記録する。
Marpスライド作成ガイド(共通ルール)
複数のMarpスライドリポジトリで共通して適用されるルール。
新規スライドの作成
slides/またはslides/YYYY/配下にYYYY-MM スライド名形式でフォルダを作成- フォルダ内にマークダウンファイルを作成
- 以下のフロントマターで開始:
---
marp: true
paginate: true
theme: テーマ名
---
各フォルダには以下を配置:
- マークダウンファイル(スライド本体)
- 画像ファイル(スライド固有のもの)
- 関連資料(検討メモ、参考資料など)
画像参照
スライド内の画像は同じフォルダに置き、相対パスで参照:

共通テーマクラス
| クラス | 用途 |
|---|---|
top |
タイトルスライド(中央寄せ、ページ番号非表示) |
crosshead |
セクション区切り(中央寄せ、ページ番号非表示) |
<!-- _class: top -->
# タイトル
スライド固有のスタイルカスタマイズ
テーマを使用しつつ、特定のスライドだけスタイルを変更したい場合のルール。
基本ルール
<style>タグをフロントマターの直後に配置- 絶対値(pt)で指定(em や % は避ける)
!importantを付けてテーマを上書き
<style>
h1 { font-size: 36pt !important; }
p, li { font-size: 22pt !important; }
</style>
なぜこの方法か
| 方法 | 結果 |
|---|---|
フロントマターの style: |
テーマとの相性で表示されないことがある |
| em / % での相対指定 | テーマの基準値と合わず予期しないサイズになる |
<span style="..."> 等のインラインスタイル |
Marpがセキュリティ上サニタイズするため無視される |
<style>タグ + pt + !important |
確実にテーマを上書きできる |
特定要素だけスタイルを変えたい場合
<style> タグでカスタムクラスを定義し、HTML タグの class 属性で適用する。
<style>
.name { font-size: 30pt !important; font-weight: bold !important; color: #ffffff !important; }
</style>
<span class="name">@minorun365</span>
Marp はインラインの style 属性(<span style="...">)をサニタイズして無視するため、必ず <style> タグ + class 属性の組み合わせで使うこと。
特定スライドだけスタイルを変えたい場合(scoped)
<style scoped> を使うと、そのスライドだけにスタイルを適用できる。
<style scoped>
p { font-size: 36pt !important; }
</style>
スライドのテキストスタイル
AIっぽい文章にしないこと。以下のルールを厳守する。
- 本文中に太字(
**...**)を多用しない。強調したい場合でもベタ書きで十分 - コロン(
:)を区切りとして使わない(例:**項目名**: 説明文はNG) - ダッシュ(
──)を使わない - 箇条書きの項目は「太字ヘッダー + コロン + 本文」ではなく、本文のみベタ書きにする
- 箇条書き・表・コードブロックの後にテキストを続ける場合は
<br>タグを1つ挟んで行間を空ける - アジェンダスライドは設けない
- まとめスライドも設けない(限られた時間を中身に集中させるため)
情報密度
- 1スライド1メッセージを徹底
- 箇条書きは3-4項目が上限、1項目1-2文
- 箇条書きの階層は基本1階層(深くても2階層まで)
- 段階的ビルドアップ(同じスライドを複数枚用意し、要素を1つずつ追加して理解を積み上げる)
コマンド
PDF出力(VS Code Marp拡張)
⌘+⇧+P→ 「Marp: Export Slide Deck」を選択- 出力形式(PDF/HTML/PPTX)を選ぶ
Marp CLI
# インストール
npm install -g @marp-team/marp-cli
# PDF出力(ローカル画像を含む場合は --allow-local-files が必須)
marp slides/XXX/XXX.md --pdf --theme theme/テーマ名.css --allow-local-files
Marp Core(ブラウザ用)
基本的な使い方
import Marp from '@marp-team/marp-core';
const marp = new Marp();
const { html, css } = marp.render(markdown);
// SVG要素を抽出(DOM構造を維持)
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const svgs = doc.querySelectorAll('svg[data-marpit-svg]');
スライド表示
<style>{css}</style>
<div className="marpit w-full h-full [&>svg]:w-full [&>svg]:h-full">
<div dangerouslySetInnerHTML={{ __html: svg.outerHTML }} />
</div>
重要: sectionだけ抽出するとCSSセレクタがマッチしない。div.marpit > svg > foreignObject > section 構造が必要。
iOS Safari対応(必須)
iOS Safari/Chromeでスライドが見切れる問題がある。これはWebKit Bug 23113(15年以上放置)が原因で、<foreignObject>内のHTMLがviewBox変換を正しく継承しない。
解決策: marpit-svg-polyfillを使用
npm install @marp-team/marpit-svg-polyfill
import { useEffect, useRef } from 'react';
import { observe } from '@marp-team/marpit-svg-polyfill';
function SlidePreview({ markdown }) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (containerRef.current) {
const cleanup = observe(containerRef.current);
return cleanup;
}
}, [markdown]);
return (
<div ref={containerRef}>
{/* スライド表示 */}
</div>
);
}
注意: Chrome DevToolsのiOSエミュレーションでは再現しない(内部エンジンが異なるため)。実機テストが必須。
Tailwind CSSとの競合
invertクラスの競合
Marpのclass: invertとTailwindの.invertユーティリティが競合する。
/* src/index.css に追加 */
.marpit section.invert {
filter: none !important;
}
箇条書き(リストスタイル)の競合
Tailwind CSS v4のPreflight(CSSリセット)がlist-style: noneを適用するため、Marpスライド内の箇条書きビュレット(●○■)が消える。
注意: list-style(ショートハンド)ではなく list-style-type(個別プロパティ)を使うこと。ショートハンドだと list-style-position も暗黙的にリセットされ、テーマ側の設定が上書きされる。
/* src/index.css に追加 */
.marpit ul {
list-style-type: disc !important;
}
.marpit ol {
list-style-type: decimal !important;
}
/* ネストされたリストのスタイル */
.marpit ul ul,
.marpit ol ul {
list-style-type: circle !important;
}
.marpit ul ul ul,
.marpit ol ul ul {
list-style-type: square !important;
}
SVGのレスポンシブ対応(スマホ対応)
MarpのSVGは固定サイズ(1280x720px)のwidth/height属性を持っているため、スマホの狭い画面では見切れる。SVG属性を動的に変更して対応:
const svgs = doc.querySelectorAll('svg[data-marpit-svg]');
return Array.from(svgs).map((svg, index) => {
// SVGのwidth/height属性を100%に変更してレスポンシブ対応
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
return { index, html: svg.outerHTML };
});
ポイント:
width/heightを100%に → 親要素にフィットpreserveAspectRatio="xMidYMid meet"→ アスペクト比維持で中央配置- CSSの
!importantよりSVG属性の直接変更が確実
汎用パターン: 外部ライブラリが生成する固定サイズSVGをレスポンシブにする場合に有効
カスタムテーマ
テーマの追加方法
import Marp from '@marp-team/marp-core';
import customTheme from '../themes/custom.css?raw'; // Viteの?rawでCSSを文字列として読み込み
const marp = new Marp();
marp.themeSet.add(customTheme); // カスタムテーマを登録
const { html, css } = marp.render(markdown);
コミュニティテーマの利用
Marpコミュニティテーマ(例: border)を使う場合:
- CSSファイルをダウンロード
src/themes/に配置?rawサフィックスでインポートmarp.themeSet.add()で登録
参考: https://rnd195.github.io/marp-community-themes/
テーマ統一ディレクティブ(テーマ切り替え互換性)
複数テーマ間で切り替え可能にするため、全テーマで統一されたCSSクラスベースのディレクティブを使う。デザイン差はCSSのみで吸収する。
| 用途 | ディレクティブ |
|---|---|
| タイトルスライド | <!-- _class: lead --><!-- _paginate: skip --> |
| セクション区切り | <!-- _class: lead --> |
| 参考文献スライド | <!-- _class: tinytext --> |
NG: テーマ固有のインラインスタイル(<!-- _backgroundColor: #303030 --><!-- _color: white -->)はテーマ切り替え時に崩れる。
フロントエンドの正規化: 旧スタイルの既存スライドは SlidePreview.tsx の useMemo 内で自動的に統一クラスに変換する。
Gaiaベーステーマの注意(Speee等)
@import "default" を使わないGaiaベースのテーマは、リスト余白やビュレット位置のデフォルトスタイルが欠落する。以下を明示的に設定する:
ul, ol {
padding-left: 0;
list-style-position: inside; /* ビュレットをテキスト開始位置に揃える */
margin-top: 0.6em; /* 見出し・テキストとの余白 */
}
ul ul, ul ol, ol ul, ol ol {
padding-left: 1.5em;
margin-top: 0;
}
フロントエンドとバックエンドの両方に配置
カスタムテーマを使う場合、以下の両方に配置が必要:
src/themes/xxx.css- フロントエンド(Marp Core)用amplify/agent/runtime/xxx.css- バックエンド(Marp CLI PDF生成)用
PDF生成時は --theme オプションでCSSファイルを指定:
cmd = ["marp", md_path, "--pdf", "--theme", str(theme_path)]
Marp CLI
出力オプション
| オプション | 出力形式 | 依存 | 編集可能 |
|---|---|---|---|
--pdf |
なし | ❌ | |
--pptx |
PPTX | なし | ❌ |
--pptx-editable |
PPTX(編集可能) | LibreOffice必須 | ✅ |
--html |
HTML | なし | - |
注意: --pptx-editable はLibreOfficeの soffice バイナリに依存する。Dockerコンテナ等でLibreOfficeがインストールされていない環境では以下のエラーが発生:
[EXPERIMENTAL] Converting to editable PPTX is experimental feature.
[ERROR] Failed converting Markdown. (LibreOffice soffice binary could not be found.)
→ LibreOffice不要な環境では --pptx(標準PPTX)を使用する。
Docker環境でのPDF生成
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium \
fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/* \
&& fc-cache -fv
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ポイント:
chromium- PDF生成に必須fonts-noto-cjk- 日本語の豆腐文字(□)防止
Marp記法の注意点
==ハイライト== 記法は使用禁止
Marpの ==テキスト== ハイライト記法は、日本語のカギカッコと組み合わせるとレンダリングが壊れる。
<!-- NG: 正しく表示されない -->
==「重要」==
<!-- OK: 太字を使う -->
**「重要」**
LLMにスライド生成させる場合は、システムプロンプトで禁止指示を入れておくこと。
トラブルシューティング
スライドのCSSが適用されない
症状: スライドのスタイルが正しく表示されない
原因: section要素だけを抽出してDOM構造が崩れた
解決策: SVG要素をそのまま使い、div.marpitでラップする
<div className="marpit">
<div dangerouslySetInnerHTML={{ __html: svg.outerHTML }} />
</div>
PDF出力でエラー
症状: Dockerコンテナ内でPDF生成に失敗
原因: Chromiumがインストールされていない
解決策: Dockerfileに追加
RUN apt-get update && apt-get install -y chromium
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
PDF日本語文字化け(豆腐文字)
症状: PDFをダウンロードすると日本語が□(豆腐)で表示される
原因: Dockerコンテナに日本語フォントがない
解決策: Dockerfileに日本語フォントを追加
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium \
fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/* \
&& fc-cache -fv
複数出力形式でテーマ設定が一部だけ反映される
症状: PDF出力では正しいテーマが適用されるが、PPTX出力では常に同じテーマが使われる
原因: 出力形式ごとに別関数を作成した際、一方の関数でテーマをハードコードしていた
解決策: すべての出力関数で環境変数を一貫して使用する
THEME_NAME = os.environ.get("MARP_THEME", "border")
def generate_pdf(markdown: str) -> bytes:
theme_path = Path(__file__).parent / f"{THEME_NAME}.css"
...
def generate_pptx(markdown: str) -> bytes:
theme_path = Path(__file__).parent / f"{THEME_NAME}.css" # 同じ方式に統一
...
カスタムテーマ: position: absolute が効かない(defaultテーマとの競合)
症状: カスタムテーマで section.top p { position: absolute; bottom: 0; left: 0; width: 100%; } を設定しても、p要素が意図した位置に配置されない(右に寄る等)
原因: @import 'default' で読み込まれるMarpデフォルトテーマのスタイルが、カスタムテーマのスタイルと競合して上書きされる
解決策: 位置・レイアウト系プロパティに !important を追加して確実に適用
section.top p {
position: absolute !important;
bottom: 0 !important;
left: 0 !important;
width: 100% !important;
height: 33% !important;
display: flex !important;
flex-direction: column;
align-items: center !important;
justify-content: center !important;
margin: 0 !important;
text-align: center;
box-sizing: border-box;
z-index: 1;
}
教訓: @import 'default' を使うカスタムテーマでは、レイアウト系プロパティ(position, display, margin等)に !important を付けないとデフォルトテーマに負ける場合がある
カスタムテーマ: 複数の <p> 要素が重なる
症状: タイトルスライドで所属と名前を空行で分けて書くと、2つのp要素が同じ位置に重なって表示される
原因: Markdownで空行を挟むと別々の <p> 要素になる。position: absolute で同じ座標に配置されるため重なる
<!-- NG: 2つの<p>要素が生成される -->
KDDIアジャイル開発センター株式会社
テックエバンジェリスト みのるん
解決策: <br> で結合して1つの <p> 要素にまとめる
<!-- OK: 1つの<p>要素 -->
KDDIアジャイル開発センター株式会社<br>テックエバンジェリスト みのるん
補足: CSSの flex-direction: column を併用すると、<br> による改行が自然に縦に並ぶ
テーマ確認(デバッグ)
スライドに適用されているテーマを確認するには、ブラウザDevToolsで:
// section要素のdata-theme属性を確認
document.querySelectorAll('section[data-theme]')
参考リンク
Source: minorun365/my-claude-code-settings — distributed by TomeVault.