Use when you need to add a new element shape to the LikeC4 codebase. Trigger this skill whenever the user mentions adding a shape, creating a new shape, implementing a shape, or asks about how shapes work in LikeC4. Also trigger when the user provides a visual reference image and wants it turned into an element shape. Even if the user just says "new shape" or drops an image of a shape they want — use this skill.
You are an agent implementing a new element shape in the LikeC4 codebase. Follow this workflow phase by phase. Do not skip phases or reorder steps. Pause at marked checkpoints and wait for user confirmation before continuing.
You need two things before starting:
A reference image — a visual showing what the shape should look like. If the user hasn't provided one, ask:
I need a visual reference for the shape — a screenshot, sketch, or example from another tool. Can you share one?
The shape name — a single lowercase word, no hyphens or underscores (e.g., hexagon, cloud, diamond). If the user hasn't specified one, propose a name based on the reference image and confirm it.
Before writing any code, determine:
component's corner squares, or browser's address bar)?Read the existing shapes to inform your decision:
view packages/diagram/src/base-primitives/element/ElementShape.tsx
Study the ShapeSvg function. Identify which case block is closest to what you need to build.
The goal is to get a visual prototype in front of the user as fast as possible, without changing types, grammar, or any wiring files.
Pick the closest existing shape (from Phase 0) and comment out its rendering, replacing it with your new SVG implementation under the same case. This lets the shape render in a running app immediately.
Use str_replace to edit packages/diagram/src/base-primitives/element/ElementShape.tsx:
case 'cylinder': {
// --- ORIGINAL (commented out for sketch) ---
// svg = <CylinderShape w={w} h={h} size={size} />
// --- SKETCH: "YOUR_SHAPE" ---
svg = (
<g>
{/* Your new SVG implementation */}
</g>
)
break
}
Also update the corresponding case in ShapeSvgOutline if the shape has a non-rectangular outline.
data-likec4-fill attribute (inherits from parent)data-likec4-fill="mix-stroke" for stroke-fill mix coloringdata-likec4-fill="fill" for fill-colored sub-elementsstrokeWidth={0} for filled areas, strokeWidth={2} for outlined strokessize parameter ('xs' | 'sm' | 'md' | 'lg' | 'xl')| Pattern | Examples | Use when |
|---|---|---|
| Rectangle + decorations | 'component', 'person' | Rectangle with extras |
| Custom SVG path | 'cylinder', 'queue', 'bucket', 'document' | Curved or non-rectangular outline |
| Box with inner header/chrome | 'browser', 'mobile' | Container with UI elements |
Present the result to the user:
Here's the sketch of the
YOUR_SHAPEshape. I've temporarily replacedcylinderto render it. Take a look and let me know if you'd like to adjust anything — proportions, stroke treatment, responsive sizing, sub-element placement, etc.
WAIT FOR USER APPROVAL. Iterate on the sketch as many times as needed. Only proceed to Phase 2 after explicit confirmation.
Now that the visual is approved, register the shape across the codebase. This phase is fully mechanical — no user input needed.
File: styled-system/preset/src/defaults/types.ts
Read the file, find the ElementShapes array, and add your shape name. This is the single source of truth — ElementShape type and PandaCSS conditions are derived from it.
view styled-system/preset/src/defaults/types.ts
Use str_replace to add the shape to the array.
File: packages/language-server/src/like-c4.langium
Find the ElementShape returns string: rule and add the new shape to the alternation.
view packages/language-server/src/like-c4.langium
Run the helper script to update all three tmLanguage files at once:
python3 /path/to/skill/scripts/update_tmgrammars.py YOUR_SHAPE
The script updates:
packages/vscode/likec4.tmLanguage.jsonapps/playground/likec4.tmLanguage.jsonapps/docs/likec4.tmLanguage.jsonVerify the script succeeded by spot-checking one file:
view packages/vscode/likec4.tmLanguage.json
Go back to packages/diagram/src/base-primitives/element/ElementShape.tsx:
case.case 'YOUR_SHAPE': block in ShapeSvg with the approved SVG.case 'YOUR_SHAPE': in ShapeSvgOutline — or let it fall through to the default (rounded rect) if the shape is rectangular.File: packages/diagram/src/context/IconRenderer.tsx
Read the file, find the ShapeIcons object, and add a mapping. The icon must be from @tabler/icons-react and be a ForwardRefExoticComponent. Choose an icon that visually represents the shape. Fallback: IconRectangularPrism.
For each generator file, read it, find the shape switch, and add a case. If there's no native equivalent in the target format, fall through to rectangle.
| File | Format |
|---|---|
packages/generators/src/drawio/generate-drawio.ts | DrawIO |
packages/generators/src/puml/generate-puml.ts | PlantUML |
packages/generators/src/mmd/generate-mmd.ts | Mermaid |
packages/generators/src/d2/generate-d2.ts | D2 |
For each one: view the file → find the switch → str_replace to add the case.
File: packages/language-server/src/lsp/CompletionProvider.spec.ts
Find the expectedItems array for shape completions and add the shape in the same order as the Langium grammar.
Evaluate these based on your shape's characteristics from Phase 0. Skip if not needed:
Shape-specific padding (only if decorations overlap text area):
styled-system/preset/src/recipes/elementNodeData.ts_shapeYourShape: { paddingLeft: ... } (condition name is auto-generated from ElementShapes)Shape-specific CSS parts (only if custom sub-elements need their own styles):
styled-system/preset/src/recipes/elementShape.tssvg variantLayout engine sizing (only if non-standard bounding box):
packages/layouts/src/graphviz/DotPrinter.tsaddNode method with adjusted width/marginFile: e2e/src/likec4/views.c4
Add a style rule using the new shape:
style some_element {
shape YOUR_SHAPE
}
Run these commands in order and report results:
pnpm generate # Regenerate Langium parser + styled-system types
If pnpm generate fails, check your grammar syntax in the .langium file.
pnpm typecheck
If typecheck fails, look at the errors — nonexhaustive() will tell you exactly which switch statements are missing the new shape. Fix each one.
pnpm test
Snapshot files will auto-update:
packages/generators/src/mmd/__snapshots__/generate-mmd.spec.ts.snappackages/generators/src/puml/__snapshots__/generate-puml.spec.ts.snappnpm build
If pnpm typecheck reports missing switch cases (via nonexhaustive()), this is the type system telling you which files you missed. Read the error, find the file, add the missing case. Re-run pnpm typecheck until clean.
pnpm changeset
Select affected packages (at minimum @likec4/diagram and likec4), choose patch, description: "Add new YOUR_SHAPE element shape".
Summarize what was done:
✅ The
YOUR_SHAPEshape is fully implemented. Here's what I changed:Core: types.ts, like-c4.langium, ElementShape.tsx, IconRenderer.tsx Syntax: 3 tmLanguage files Exports: DrawIO, PlantUML, Mermaid, D2 Tests: CompletionProvider.spec.ts, e2e/views.c4 Conditional: [list any conditional files that were edited, or "none needed"]
All checks pass: generate ✅ typecheck ✅ test ✅ build ✅
ElementShape type is derived from the ElementShapes array in types.ts — this is the single source of truth._shapeYourShape) are auto-generated from ElementShapes via styled-system/preset/src/conditions.ts:18-21.packages/language-server/src/generated/, styled-system/styles/dist/) are rebuilt by pnpm generate. Never edit manually.schemas/likec4-config.schema.json is auto-generated from types. Do not edit manually.nonexhaustive() from @likec4/core enforces exhaustive switch coverage — TypeScript errors guide you to missing cases.ForwardRefExoticComponent from @tabler/icons-react.