excalidraw-generation
from rhuss/cc-slidev
Claude Code plugin for creating developer-focused technical presentations using Slidev with evidence-based design guardrails
npx skills add https://github.com/rhuss/cc-slidev --skill excalidraw-generationSKILL.md
Excalidraw Generation Expert
Core Philosophy: Semantic redesign, not mechanical conversion. Think like both a presentation designer (clarity, accessibility, simplicity) and an artist (creative visual expression, spatial design, aesthetic beauty).
CRITICAL - Rendering Rule
ALWAYS use render-excalidraw.sh for SVG conversion - NO EXCEPTIONS
After creating Excalidraw JSON:
- Save JSON to
diagrams/<slug>.excalidraw - MUST render using:
${CLAUDE_PLUGIN_ROOT}/scripts/render-excalidraw.sh - NEVER attempt manual SVG conversion
- NEVER embed JSON in markdown - only reference the rendered SVG
The script handles all rendering automatically with excalidraw-brute-export-cli.
When to Use This Skill
Auto-trigger when:
- User explicitly requests: "create excalidraw diagram", "hand-drawn diagram", "sketch", "whiteboard"
- Slide content suggests conceptual/spatial relationships
- Architecture with nested components
- Brainstorming/ideation context
- Informal, approachable style needed
- Annotations and callouts would add value
Diagram type suitability:
- ✅✅✅ BEST: Conceptual relationships, architecture diagrams, mind maps, timelines, comparisons
- ✅✅ EXCELLENT: Flowcharts (with annotations), spatial layouts, nested structures
- ⚠️ OKAY: Sequence diagrams (prefer Mermaid instead)
- ❌ NOT RECOMMENDED: Formal UML (use PlantUML), state machines (use Mermaid)
Evidence-Based Design Constraints (HARD LIMITS)
These constraints are NON-NEGOTIABLE. Enforce strictly:
- Cognitive load: Maximum 9 elements (7±2 rule from cognitive psychology)
- Accessibility:
- Colorblind-safe palette ONLY: Blue #3b82f6 + Orange #f97316
- Minimum 4.5:1 contrast ratio for all text (WCAG AA)
- Never rely on color alone to convey information
- Minimal text: Under 50 words total per diagram
- One idea per diagram: If concept is complex, split into multiple diagrams
- Hand-drawn aesthetic: Roughness 1 for informal feel
Core Capabilities
1. Semantic Concept Extraction
Process (ALWAYS follow this order):
-
Analyze user's description or slide content
-
Extract core concepts (entities, relationships, flows)
-
Identify semantic type:
- Containment: X contains Y → Use nested boxes/frames
- Flow: A→B→C → Use arrows with spatial progression
- Comparison: X vs Y → Use side-by-side separation
- Hierarchy: Parent-child → Use vertical/spatial positioning
- Grouping: Related items → Use frames or color-coded regions
- Annotation: Context/explanation → Use callouts and bound text
-
Design layout (choose from layout algorithms below)
-
Generate JSON (use element factories below)
Example:
Input: "Kubernetes device plugin architecture"
Semantic analysis:
- Type: Architecture + Flow
- Key concepts: Control Plane (container), Worker Node (container),
GPU (component), Device Plugin (component), Kubelet (component)
- Relationships: Discovery flow, Registration flow, Capacity updates
- Spatial meaning: Control Plane ABOVE Worker Node (hierarchy)
Design choice: Vertical layout with 2 frames, 5 shapes, 3 arrows, 3 annotations
Cognitive load: 2 frames + 5 shapes = 7 units ✓
2. Element Factories
These functions generate valid Excalidraw JSON elements. Use them to build diagrams.
ID Generation
function generateId() {
// Excalidraw uses random alphanumeric IDs (12+ chars)
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}
Rectangle Factory
function createRectangle(x, y, width, height, text = null, options = {}) {
const id = generateId();
const element = {
type: "rectangle",
version: 1,
versionNonce: Math.floor(Math.random() * 1000000),
isDeleted: false,
id: id,
fillStyle: options.fillStyle || "hachure",
strokeWidth: options.strokeWidth || 2,
strokeStyle: "solid",
roughness: options.roughness !== undefined ? options.roughness : 1,
opacity: 100,
angle: options.angle || 0,
x: x,
y: y,
strokeColor: options.strokeColor || THEME_COLORS.primary,
backgroundColor: options.backgroundColor || "transparent",
width: width,
height: height,
seed: Math.floor(Math.random() * 1000000),
groupIds: options.groupIds || [],
frameId: options.frameId || null,
roundness: { type: 3 },
boundElements: [],
updated: Date.now(),
link: null,
locked: false
};
// If text provided, create bound text element
if (text) {
const textElement = createBoundText(text, id, x, y, width, height);
element.boundElements.push({ type: "text", id: textElement.id });
return [element, textElement]; // Return array
}
return element; // Return single element
}
Text Factory (Standalone and Bound)
function createText(text, x, y, options = {}) {
return {
type: "text",
version: 1,
v
...