UI customization
Este conteúdo não está disponível em sua língua ainda.
Some parts of the LikeC4 UI are slots: you can replace them with your own React components, or wrap the built-in ones.
You describe the overrides in a customization file, and LikeC4 applies them to the application it builds, without changes in its code:
import { defineCustomComponents } from 'likec4/react'
export default defineCustomComponents(({ wrap }) => ({ // Replace: the component receives the props of the slot LogoSvg: props => ( <svg viewBox="0 0 120 32" {...props}> <text x="0" y="24" fontSize="24" fill="currentColor">Acme</text> </svg> ), // Wrap: `Original` is the built-in component LikeC4Diagram: wrap(({ Original, ...props }) => ( <Original {...props} showNavigationButtons={false} /> )),}))This page is about UI customization with React components.
To change colors, sizes and other styles without code, use style customization (styles in the project config) instead.
To see how the customization file relates to the other config files, read Configuration files.
Customization file
Section titled “Customization file”The file must default-export the result of defineCustomComponents from likec4/react.
The file can have any name, because LikeC4 does not look for it. There are three ways to point LikeC4 to it:
| Where | How | The path is relative to |
|---|---|---|
| App config | customization: './likec4.customization.tsx' |
the config file |
| CLI | --customization ./likec4.customization.tsx |
the current directory |
| Vite plugin | customization option |
the root of the Vite project |
It is an error if the file does not exist.
With the CLI, the overrides are applied by:
likec4 startandlikec4 build: to the applicationlikec4 export pngandlikec4 export jpg: to the exported images (not with--server-url, see App config)likec4 gen reactandlikec4 gen webcomponent: to the generated components
With the Vite plugin, the overrides are applied to the components from likec4:react.
Hot reload
Section titled “Hot reload”likec4 start and the Vite dev server watch the customization file.
When you edit it, or a file it imports, the overrides are updated in the browser without reloading the page.
Imports
Section titled “Imports”With the CLI, the customization file is compiled together with the LikeC4 application:
react,react-dom,likec4/reactandlikec4/modelalways resolve to the copies used by the application. You do not need to install them next to the file, but you may want to, for types in your editor.- Other packages (like
roughjsin the example below) are resolved fromnode_modulesnext to the file, as usual. Install them yourself.
Replace and wrap
Section titled “Replace and wrap”defineCustomComponents takes an object, or a function that receives the wrap helper:
- Replace: give a component. It receives the same props as the built-in one.
- Wrap: give
wrap(Component). Your component receives the props andOriginal, the built-in component. Use it to change props, or to render something around the built-in component.
import { defineCustomComponents } from 'likec4/react'
export default defineCustomComponents(({ wrap }) => ({ // The built-in shape, without the outline of the selected node ElementShape: wrap(({ Original, ...props }) => ( <Original {...props} showSelectionOutline={false} /> )),}))Slots you do not mention keep the built-in components.
| Slot | Built-in component | Props |
|---|---|---|
LikeC4Diagram |
The diagram: the canvas with nodes, edges and controls. The application renders it for every view | LikeC4DiagramProps |
LikeC4View |
LikeC4View: a view with the browser popup |
LikeC4ViewProps |
ReactLikeC4 |
ReactLikeC4: the low-level component |
ReactLikeC4Props |
LogoSvg |
LikeC4 logo with the name, in the navigation panel | SVG props |
LogoIconSvg |
LikeC4 logo without the name, in the navigation panel | SVG props |
ElementShape |
Shape of an element: rectangle, cylinder, browser and so on | ElementShapeProps |
The types of the props are exported from likec4/react. The names of the slots are checked by TypeScript.
A component that replaces ElementShape draws the whole shape, including what the built-in one draws for
multiple elements and for the selected node. Wrap the slot if you only want to add to the built-in shape.
Example: hand-drawn shapes
Section titled “Example: hand-drawn shapes”This example replaces the shapes with hand-drawn ones, sketched by Rough.js.
Nodes stay the default ones: titles, icons, colors and navigation work as before, only the shape is drawn differently.
The complete project, with all the shapes, is in examples/customization-roughjs.
-
Install Rough.js next to your LikeC4 sources:
Terminal window npm install roughjs -
Draw the shape. The slot receives the shape of the element and the size of the node:
RoughShape.tsx import type { ElementShapeProps } from 'likec4/react'import { useId, useMemo } from 'react'import rough from 'roughjs'// Colors of the node are CSS variables, so themes and element colors keep workingconst FILL = 'var(--likec4-palette-fill)'const STROKE = 'var(--likec4-palette-stroke)'function silhouette(shape: string, w: number, h: number): string {switch (shape) {case 'cylinder':case 'storage': {const ry = Math.round(w * 0.035)return `M${w},${ry} A${w / 2},${ry} 0 0 0 0,${ry} V${h - ry} A${w / 2},${ry} 0 0 0 ${w},${h - ry} Z`}// ...other shapesdefault:return `M0,0 H${w} V${h} H0 Z`}}// Every node gets its own sketch, the same on each renderfunction seedOf(id: string): number {let hash = 0for (let i = 0; i < id.length; i++) {hash = (hash * 31 + id.charCodeAt(i)) | 0}return Math.abs(hash) + 1}function sketch(shape: string, width: number, height: number, seed: number) {const gen = rough.generator({options: { seed, roughness: 1.8, stroke: STROKE, strokeWidth: 2.5 },})const body = silhouette(shape, width, height)return [// Solid fill keeps the text readablegen.path(body, { stroke: 'none', fill: FILL, fillStyle: 'solid', roughness: 0 }),// Hatching and the hand-drawn outline on top of itgen.path(body, { fill: STROKE, fillStyle: 'hachure', hachureGap: 8 }),].flatMap(drawable => gen.toPaths(drawable))}export function RoughShape({ data, width: w, height: h }: ElementShapeProps) {const id = useId()const { shape } = dataconst width = w && w > 10 ? w : data.widthconst height = h && h > 10 ? h : data.height// The slot is rendered for every node, so the sketch is memoizedconst paths = useMemo(() => sketch(shape, width, height, seedOf(id)),[shape, width, height, id],)return (<svgaria-hiddenviewBox={`0 0 ${width} ${height}`}style={{position: 'absolute',inset: 0,width: '100%',height: '100%',overflow: 'visible',pointerEvents: 'none',}}>{paths.map((path, i) => (<pathkey={i}d={path.d}// CSS variables have to be set through `style`style={{fill: path.fill ?? 'none',stroke: path.stroke,strokeWidth: path.strokeWidth,}}/>))}</svg>)} -
Replace the slot in the customization file:
likec4.customization.tsx import { defineCustomComponents } from 'likec4/react'import { RoughShape } from './RoughShape'export default defineCustomComponents({ElementShape: RoughShape,}) -
Point LikeC4 to the file in the app config:
likec4.app.config.ts import { defineAppConfig } from 'likec4/config'export default defineAppConfig({title: 'Rough.js shapes',customization: './likec4.customization.tsx',}) -
Start the dev server and edit
RoughShape.tsx: the diagrams are redrawn as you save.Terminal window likec4 start