Components
At a glance
Mitosis is inspired by many modern frameworks. You'll see components that look like React components and use React-like hooks, but have simple mutable state like Vue, use a static form of JSX like Solid, compile away like Svelte, and use a simple, prescriptive structure like Angular.
An example of a Mitosis component showing several features:
import { For, Show, useStore } from '@builder.io/mitosis';
export default function MyComponent(props) {
const state = useStore({
newItemName: 'New item',
list: ['hello', 'world'],
addItem() {
state.list = [...state.list, state.newItemName];
},
});
return (
<div>
<Show when={props.showInput}>
<input
value={state.newItemName}
onChange={(event) => (state.newItemName = event.target.value)}
/>
</Show>
<div css={{ padding: '10px' }}>
<button onClick={() => state.addItem()}>Add list item</button>
<div>
<For each={state.list}>{(item) => <div>{item}</div>}</For>
</div>
</div>
</div>
);
}
Components
Mitosis is component-driven like most modern frontend frameworks. Each Mitosis component should be in its own file and be the single default export. They are simple functions that return JSX elements
export default function MyComponent() {
return <div>Hello world!</div>;
}
State
State is provided by the useStore
hook. Currently, the name of this value must be state
like below:
export default function MyComponent() {
const state = useStore({
name: 'Steve',
});
return (
<div>
<h2>Hello, {state.name}</h2>
<input
onInput={(event) => {
state.name = event.target.value;
}}
value={state.name}
/>
</div>
);
}
If the initial state value is a computed value (whether based on props
or the output of some function), then you cannot inline it. Instead, use a getter method:
export default function MyComponent(props) {
const state = useStore({
_name: '',
get name() {
// Use the state value if set, otherwise the prop value if set,
// otherwise the default value of 'Steve'
return state._name || props.name || 'Steve';
},
setName(name: string) {
state._name = name;
},
});
return (
<div>
<h2>Hello, {state.name}</h2>
<input onInput={(event) => state.setName(event.target.value)} value={state.name} />
</div>
);
}
Components automatically update when state values change
useState
Alternatively, you can use the useState
hook to create a single piece of state
export default function MyComponent() {
const [name, setName] = useState('Steve');
return (
<div>
<h2>Hello, {name}</h2>
<input onInput={(event) => setName(event.target.value)} value={name} />
</div>
);
}
Methods
The state object can also take methods.
export default function MyComponent() {
const state = useStore({
name: 'Steve',
updateName(newName) {
state.name = newName;
},
});
return (
<div>
<h2>Hello, {state.name}</h2>
<input onInput={(event) => state.updateName(event.target.value)} value={state.name} />
</div>
);
}
Styling
css-in-js
Styling is done via the css
prop on DOM elements and components. It takes CSS properties in camelCase
(like the style
object on DOM elements) and properties as valid CSS strings
export default function CSSExample() {
return <div css={{ marginTop: '10px', color: 'red' }} />;
}
You can also include media queries as keys, with values as style objects
export default function ResponsiveExample() {
return (
<div
css={{
marginTop: '10px',
'@media (max-width: 500px)': {
marginTop: '0px',
},
}}
/>
);
}
You can also use the useStyle hook to add styles to a component
import .css file
In general all imports inside a Mitosis component are passed to the generated output file.
Therefore, you can import a .css
file like this:
/* ./src/my-component/my-component.css */
.my-component{
margin-top: 10px;
}
/* ./src/my-component/my-component.lite.tsx */
import "./my-component.css";
export default function CSSExample() {
return <div class="my-component" />;
}
Note: The Mitosis cli will only move
.ts
and.js
files to the output directory. You need to move the.css
file by your own.
headless components
Another approach is to develop your components without a fixed style inside the Mitosis component, a.k.a. "headless components".
You can do so by loading the styles inside the consuming webapp via a bundler (vite, webpack, etc.) or with a head stylesheet <link rel="stylesheet" ...
.
You can then use class
or data-*
attributes inside your Mitosis component to apply those styles:
/* ./src/my-component/my-component.lite.tsx */
export default function CSSExample() {
return <div class="my-component" />;
}
class
vs className
Mitosis prefers that you use class
to provide class name strings, but it also allows you to provide className
. If both are used in the same component, it will attempt to merge the two. We recommend that you only use one (preferrably class
, as that's what is internally preferred by Mitosis).
Control flow
Control flow in Builder is static like Solid. Instead of using freeform javascript like in React, you must use control flow components like <Show>
and <For>
Show
Use <Show>
for conditional logic. It takes a single when
prop for the condition to match. When the condition is truthy, the children will render; otherwise, they will not. You can additionally provide an else
prop for the content to be rendered in case the condition is falsy.
export default function MyComponent(props) {
return (
<>
<Show when={props.showContents} else={<span {...props.attributes}>{props.text}</span>}>
Hello, I may or may not show!
</Show>
;
</>
);
}
For
Use <For>
for repeating items, for instance mapping over an array. It takes a singular each
prop for the array to iterate over. This component takes a singular function as a child that it passes the relevant item and index to, like below:
export default function MyComponent(props) {
const state = useStore({
myArray: [1, 2, 3],
});
return <For each={state.myArray}>{(theArrayItem, index) => <div>{theArrayItem}</div>}</For>;
}
Children
We use the standard method for passing children with props.children
export default function MyComponent(props) {
return <div>{props.children}</div>;
}
For Web Component you need to use ShadowDom metadata
import { useMetadata } from '@builder.io/mitosis';
useMetadata({
isAttachedToShadowDom: true,
});
export default function MyComponent(props) {
return <div>{props.children}</div>;
}
Note: You cannot directly iterate over the children prop. It is a special property intended for rendering like this:
<div>{props.children}</div>
Many frameworks do not support manipulating or iterating over it directly, unlike frameworks such as React or Vue.
Fragment
The Fragment
component accepts the key
prop, which is typically used when the Fragment
is the direct child of a For
loop component.
import { For, Fragment } from '@builder.io/mitosis';
export default function BasicForFragment() {
return (
<div>
<For each={['a', 'b', 'c']}>
{(option) => (
<Fragment key={`key-${option}`}>
<span>{option}</span>
</Fragment>
)}
</For>
</div>
);
}
Slot
When you want to register a named slot you do so using a prop.
<div>
<Layout
top={<NavBar/>}
left={<Sidebar/>}
center={<Content/>}
/>
anything else
</Layout>
</div>
In this example we are registering top
, left
, and center
for the Layout
component to project.
If the Layout
component was also a Mitosis component then we simply use the reference in the props.
export default function Layout(props) {
return (
<div className="layout">
<div className="top">{props.top}</div>
<div className="left">{props.left}</div>
<div className="center">{props.center}</div>
{props.children}
</div>
);
}
or use the Slot component provided by component
import { Slot } from '@builder.io/mitosis';
export default function Layout(props) {
return (
<div className="layout">
<div className="top">
<Slot name="top" />
</div>
<div className="left">
<Slot name="left" />
</div>
<div className="center">
<Slot name="center" />
</div>
<Slot />
</div>
);
}
For vue component a slot
prop will be compiled into named slot
<div class="layout">
<div class="top"><slot name="top" /></div>
<div class="left"><slot name="left" /></div>
<div class="center"><slot name="center" /></div>
<slot />
</div>
}
Mitosis compiles one component at a time and is only concerned with outputting the correct method for each framework. For the two examples above, here are the angular and html outputs.
<div>
<layout>
<sidebar left></sidebar>
<nav-bar top></nav-bar>
<content center></content>
anything else
</layout>
<div></div>
</div>
@Component({
selector: 'layout',
template: `
<div class="layout">
<div class="top">
<ng-content select="[top]"></ng-content>
</div>
<div class="left">
<ng-content select="[left]"></ng-content>
</div>
<div class="center">
<ng-content select="[center]"></ng-content>
</div>
<ng-content></ng-content>
</div>
`,
})
class LayoutComponent {}
In webcomponent you need to use ShadowDom metadata for named slots
For Web Component you need to use ShadowDom metadata named slots
import { useMetadata } from '@builder.io/mitosis';
useMetadata({
isAttachedToShadowDom: true,
});
export default function Layout(props) {
return (
<div className="layout">
<div className="top">{props.top}</div>
<div className="left">{props.left}</div>
<div className="center">{props.center}</div>
{props.children}
</div>
);
}
Default Slot content
import { Slot } from '@builder.io/mitosis';
export default function Layout(props) {
return (
<div className="layout">
<div className="top">
<Slot name="top">Top default</Slot>
</div>
<div className="left">
<Slot name="left" />
</div>
<div className="center">
<Slot name="center" />
</div>
<Slot>Default child</Slot>
</div>
);
}