values across <rect>, <circle>, and <path> elements, map each visual role to a descriptive CSS class. This creates a single source of truth for the palette.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1080">
<style>
/* Visual Role Mapping */
.surface-fill { fill: #F8F9FA; }
.brand-primary { fill: #2563EB; }
.brand-secondary { fill: #10B981; }
.text-heading { fill: #111827; font-family: 'Inter', system-ui, sans-serif; }
.text-body { fill: #6B7280; font-family: 'Inter', system-ui, sans-serif; }
.text-on-brand { fill: #FFFFFF; font-family: 'Inter', system-ui, sans-serif; }
/* Global Typography Reset */
text { font-weight: 400; letter-spacing: 0.02em; }
</style>
<!-- Structure follows -->
</svg>
Why this works: CSS classes inside SVG are natively supported by the SVG 1.1/2.0 specification. Using semantic names (brand-primary vs accent) aligns with design system terminology, reducing cognitive load for developers and designers. The internal <style> block ensures the file remains self-contained, avoiding external stylesheet resolution failures in isolated viewers.
2. Apply Tokens to Structural Markup
Reference the classes directly on SVG elements. Keep text as <text> or <tspan> nodes. Never convert typography to paths.
<rect class="surface-fill" width="1080" height="1080" />
<circle class="brand-primary" cx="540" cy="420" r="180" />
<text class="text-heading" x="540" y="680" text-anchor="middle" font-size="48">
Launch Sequence
</text>
<text class="text-body" x="540" y="740" text-anchor="middle" font-size="28">
Consistent branding across every channel
</text>
<rect class="brand-secondary" x="390" y="800" width="300" height="60" rx="12" />
<text class="text-on-brand" x="540" y="838" text-anchor="middle" font-size="22">
Get Started
</text>
Architecture decision: We avoid inline fill or stroke attributes. Mixing presentation attributes with CSS classes creates specificity conflicts that vary between renderers. By relying exclusively on class-based styling, we guarantee predictable cascade behavior. Text remains as DOM nodes to preserve accessibility, searchability, and cross-editor editability.
3. Handle Font Loading Gracefully
When targeting specific typefaces, use @import within the SVG <style> block. This ensures the font resolves before rendering in browser or headless environments.
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
Why this matters: Many SVG viewers ignore external CSS, but Chromium-based renderers and modern design tools respect @import. Using display=swap prevents render-blocking while the font downloads. For production kits, self-hosting the font files and using relative paths eliminates third-party dependency risks.
4. Automate PNG Export with Playwright
Static SVGs are ideal for editing, but social platforms require rasterized PNGs. A headless Chromium pipeline guarantees pixel-exact output while respecting font loading and CSS rendering.
import { chromium, Browser, Page } from 'playwright';
async function renderSvgToPng(svgPath: string, outputPath: string, width: number, height: number) {
const browser: Browser = await chromium.launch();
const page: Page = await browser.newPage();
await page.setViewportSize({ width, height });
await page.goto(`file://${svgPath}`);
// Wait for all @import fonts to resolve
await page.waitForFunction(() => document.fonts.ready);
await page.screenshot({ path: outputPath, type: 'png' });
await browser.close();
}
// Usage
renderSvgToPng('./templates/post-01.svg', './exports/post-01.png', 1080, 1080);
Rationale: document.fonts.ready is the critical synchronization point. Without it, Playwright captures the SVG before web fonts load, resulting in fallback typography or blurry text. Setting the viewport to exact dimensions ensures the screenshot matches platform specifications (1080Γ1080 for posts, 1080Γ1920 for stories) without scaling artifacts.
Pitfall Guide
1. External Stylesheet Dependency
Explanation: Linking to an external .css file via <link> or <style src="..."> breaks in standalone SVG viewers, email clients, and many design tools. The file becomes unstyled when opened directly.
Fix: Always embed styles inside a <style> block within the SVG. If you must share tokens across files, use a build step (e.g., Vite, Rollup, or a custom Node script) to inject the shared CSS during export.
2. Text Outlining / Path Conversion
Explanation: Converting <text> nodes to vector paths (<path d="...">) destroys editability. Non-technical users cannot change copy, and accessibility tools lose semantic meaning.
Fix: Keep all typography as <text> or <tspan> elements. If a specific renderer struggles with a font, provide a fallback stack in the CSS and verify cross-tool compatibility before distribution.
3. Mixing Presentation Attributes with CSS
Explanation: Using both fill="#FF0000" on an element and .brand-primary { fill: #0055FF; } in CSS creates unpredictable cascade behavior. Different renderers prioritize inline attributes differently.
Fix: Strip all presentation attributes from markup. Rely exclusively on CSS classes for styling. Use a linter or preprocessor to enforce this rule during asset generation.
4. Ignoring Font Loading Race Conditions
Explanation: Automated rendering pipelines that capture screenshots before fonts finish downloading produce inconsistent outputs. Some assets render with system fallbacks, others with web fonts.
Fix: Always await document.fonts.ready in headless browsers. For production CI/CD, add a retry mechanism or explicit timeout fallback to prevent pipeline hangs.
5. Hardcoded Dimensions Without viewBox
Explanation: Setting width="1080" height="1080" without a matching viewBox="0 0 1080 1080" breaks responsive scaling. The SVG will not adapt when embedded in different contexts or resized programmatically.
Fix: Always pair explicit dimensions with an identical viewBox. This ensures the coordinate system remains stable regardless of container size.
6. Over-Nesting Group Elements
Explanation: Wrapping every shape in <g> tags increases DOM complexity, slows parser performance, and makes token application harder to trace.
Fix: Apply classes directly to primitive elements (<rect>, <circle>, <path>, <text>). Use <g> only for logical grouping or transform operations.
7. Assuming Universal @import Support
Explanation: While Chromium and modern design tools support @import, some legacy viewers and email clients strip or ignore it, falling back to system fonts.
Fix: Provide a robust fallback stack (font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif). For critical brand assets, consider self-hosting fonts or using @font-face with base64-encoded subsets if file size permits.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Static marketing kits (posts, stories, banners) | CSS-driven SVG tokens + Playwright PNG export | Guarantees instant rebranding, preserves text editability, automated rasterization | Low (one-time pipeline setup) |
| Dynamic web graphics (dashboards, data viz) | CSS custom properties (--token) + JS theme switching | Enables runtime color changes without DOM manipulation | Medium (requires frontend integration) |
| Print-ready vector assets | Inline presentation attributes + CMYK conversion | Print workflows require explicit color spaces; CSS tokens may not translate to CMYK accurately | High (requires prepress tooling) |
| Email newsletter graphics | Flattened PNG with embedded fallback fonts | Email clients strip <style> blocks and @import; rasterization ensures consistent rendering | Low (automated export pipeline) |
Configuration Template
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1080" width="1080" height="1080">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
.surface-fill { fill: #F8F9FA; }
.brand-primary { fill: #2563EB; }
.brand-secondary { fill: #10B981; }
.text-heading { fill: #111827; font-family: 'Inter', system-ui, sans-serif; font-size: 48px; }
.text-body { fill: #6B7280; font-family: 'Inter', system-ui, sans-serif; font-size: 28px; }
.text-on-brand { fill: #FFFFFF; font-family: 'Inter', system-ui, sans-serif; font-size: 22px; }
text { font-weight: 400; letter-spacing: 0.02em; text-anchor: middle; }
</style>
<rect class="surface-fill" width="1080" height="1080" />
<circle class="brand-primary" cx="540" cy="420" r="180" />
<text class="text-heading" x="540" y="680">Template Title</text>
<text class="text-body" x="540" y="740">Subtitle or description</text>
<rect class="brand-secondary" x="390" y="800" width="300" height="60" rx="12" />
<text class="text-on-brand" x="540" y="838">Action Label</text>
</svg>
Quick Start Guide
- Initialize the token block: Create a new
.svg file and paste the <style> block from the Configuration Template. Replace the hex values with your brand palette.
- Build the layout: Add
<rect>, <circle>, or <path> elements for backgrounds and shapes. Apply the corresponding CSS classes. Keep all text inside <text> nodes.
- Validate rendering: Open the file in a browser and a design tool (Figma/Illustrator). Verify that colors cascade correctly and text remains editable.
- Automate export: Run the Playwright script against your SVG directory. Configure viewport dimensions to match platform specs (1080Γ1080, 1080Γ1920, etc.).
- Distribute: Ship the
.svg files for editing alongside the generated .png exports. Provide a simple token reference guide so non-technical users can swap palettes by editing six lines.