When you want your custom component to accept other blocks, configure your component with children. The following video demonstrates a custom tabs component that accepts Builder blocks as content. Each tab receives a unique block that the user drags and drops in.
To get the most out of this tutorial, you should have the following:
- An app you've integrated with Builder
- Familiarity with using custom components
The most fundamental use case is a basic container that accepts draggable content. This section demonstrates a hero component that users can drag and drop other blocks into.
Register the component with Builder, specifying that it can accept children:
import {
Builder,
withChildren
} from '@builder.io/react';
import type { PropsWithChildren } from 'react';
// Define the component
const CustomHero = (props: PropsWithChildren) => {
return (
<>
<div>This is your component's text</div>
{props.children}
</>
);
};
// IMPORTANT: withChildren is required to enable child block functionality
const HeroWithBuilderChildren = withChildren(CustomHero);
// Register with Builder
Builder.registerComponent(HeroWithBuilderChildren, {
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
});
export default CustomHero;Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
Builder.registerComponent(..., {
name: 'Your-custom-component',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
})Place the childRequirements object inside registerComponent() after defaultChildren. With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { RegisteredComponent } from '@builder.io/sdk-react';
import { ReactNode } from 'react';
interface CustomHeroProps {
children: ReactNode;
}
const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is text from your component</div>
{props.children}
</>
);
};
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import {
Builder,
withChildren
} from '@builder.io/react';
import type { PropsWithChildren } from 'react';
// Define the component
const CustomHero = (props: PropsWithChildren) => {
return (
<>
<div>This is your component's text</div>
{props.children}
</>
);
};
// IMPORTANT: withChildren is required to enable child block functionality
const HeroWithBuilderChildren = withChildren(CustomHero);
// Register with Builder
Builder.registerComponent(HeroWithBuilderChildren, {
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
});
export default CustomHero;Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
Builder.registerComponent(..., {
name: 'Your-custom-component',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
})Place the childRequirements object inside registerComponent() after defaultChildren. With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { RegisteredComponent } from '@builder.io/sdk-react';
import { ReactNode } from 'react';
interface CustomHeroProps {
children: ReactNode;
}
const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is text from your component</div>
{props.children}
</>
);
};
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import {
Builder,
withChildren
} from '@builder.io/react';
import type { PropsWithChildren } from 'react';
// Define the component
const CustomHero = (props: PropsWithChildren) => {
return (
<>
<div>This is your component text</div>
{props.children}
</>
);
};
// IMPORTANT: withChildren is required to enable child block functionality
const HeroWithBuilderChildren = withChildren(CustomHero);
// Register with Builder
Builder.registerComponent(HeroWithBuilderChildren, {
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
});
export default CustomHero;Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
Builder.registerComponent(..., {
name: 'Your-custom-component',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
})Place the childRequirements object inside registerComponent() after defaultChildren. With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { RegisteredComponent } from '@builder.io/sdk-react';
import { ReactNode } from 'react';
interface CustomHeroProps {
children: ReactNode;
}
const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is text from your component</div>
{props.children}
</>
);
};
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { RegisteredComponent } from '@builder.io/sdk-react';
import { ReactNode } from 'react';
interface CustomHeroProps {
children: ReactNode;
}
const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is text from your component</div>
{props.children}
</>
);
};
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { Builder } from '@builder.io/react';
import { ReactNode } from 'react';
// Define the component
interface CustomHeroProps {
children: ReactNode;
}
export const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is your component's text</div>
{props.children}
</>
);
};
// IMPORTANT: withChildren is required to enable child block functionality
const HeroWithBuilderChildren = withChildren(CustomHero);
// Register the component with children
Builder.registerComponent(HeroWithBuilderChildren, {
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
});Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
Builder.registerComponent(..., {
name: 'Your-custom-component',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
})Place the childRequirements object inside registerComponent() after defaultChildren. With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { RegisteredComponent } from '@builder.io/sdk-react';
import { ReactNode } from 'react';
interface CustomHeroProps {
children: ReactNode;
}
const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is text from your component</div>
{props.children}
</>
);
};
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { Builder } from '@builder.io/react';
import { ReactNode } from 'react';
// Define the component
interface CustomHeroProps {
children: ReactNode;
}
export const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is your component's text</div>
{props.children}
</>
);
};
// IMPORTANT: withChildren is required to enable child block functionality
const HeroWithBuilderChildren = withChildren(CustomHero);
// Register the component with children
Builder.registerComponent(HeroWithBuilderChildren, {
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
});Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
Builder.registerComponent(..., {
name: 'Your-custom-component',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
})Place the childRequirements object inside registerComponent() after defaultChildren. With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
<section>
<div>This is text from your component</div>
<!-- The slot will render any child blocks from Builder -->
<slot />
</section>import type { RegisteredComponent } from '@builder.io/sdk-svelte';
import CustomHero from './CustomHero.svelte';
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero', // you can change this to anything you want
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
<section>
<div>This is text from your component</div>
<!-- The slot will render any child blocks from Builder -->
<slot />
</section>import type { RegisteredComponent } from '@builder.io/sdk-svelte';
import CustomHero from './CustomHero.svelte';
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero', // you can change this to anything you want
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
The component uses the <Blocks> component to create an editable region, and the Builder registration configures how the component appears and behaves in the Visual Editor.
<template>
<section>
<div>This is text from your component</div>
<!-- The slot will render any child blocks from Builder -->
<slot />
</section>
</template>Register your component and specify that it can accept children:
import CustomHero from '@/components/custom-components/custom-hero/CustomHero.vue';
import type { RegisteredComponent } from '@builder.io/sdk-vue';
const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};
export default customHeroInfo;The component uses the <Blocks> component to create an editable region, and the Builder registration configures how the component appears and behaves in the Visual Editor.
<template>
<div>This is text from your component</div>
<!-- The slot will render any child blocks from Builder -->
<slot />
</template>Register your component and specify that it can accept children:
import type { RegisteredComponent } from '@builder.io/sdk-vue';
import CustomHero from './CustomHero.vue';
const CustomHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};
export default CustomHeroInfo;To add children to your Qwik custom component, use RegisteredComponent with defaultChildren.
The following example exports a const called customComponents, which specifies a component called MyFunComponent and has a default child Text block.
import { component$, Slot } from '@builder.io/qwik';
import type { RegisteredComponent } from '@builder.io/sdk-qwik';
export const CustomHero = component$(() => {
return (
<>
<div>This is text from your component</div>
<Slot />
</>
);
});
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};To allow only certain types of children, configure childRequirements to specify child types that it can receive. Here, it can only receive a Button, Text, or Image block.
export const customComponents: RegisteredComponent[] = [
{
component: MyFunComponent,
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in MyFunComponent',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
},
];This example uses Angular's ng-content to create an insertion point for child content. The customHeroInfo object registers the component with Builder, defining its name, available inputs, and default content, making it available in the Visual Editor.
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import type { RegisteredComponent } from '@builder.io/sdk-angular';
@Component({
selector: 'app-custom-hero',
standalone: true,
imports: [CommonModule],
template: ` <div>This is text from your component</div> `,
})
export class CustomHeroComponent {}
export const customHeroInfo: RegisteredComponent = {
component: CustomHeroComponent,
name: 'CustomHero',
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Add CustomChildComponent to your routes in your app.routes.ts:
import { Routes } from '@angular/router';
import { CustomChildComponent } from './custom-child.component';
export const routes: Routes = [
{ path: 'your-path', component: CustomChildComponent },
];This example uses Angular's ng-content to create an insertion point for child content. The customHeroInfo object registers the component with Builder, defining its name, available inputs, and default content, making it available in the Visual Editor.
import { Component, Input } from '@angular/core';
import { RegisteredComponent } from '@builder.io/sdk-angular';
@Component({
selector: 'app-custom-hero',
template: `
<div>This is your component's text</div>
<ng-content></ng-content>
`
})
export class CustomHeroComponent {
}
// Register with Builder
export const customHeroInfo: RegisteredComponent = {
component: CustomHeroComponent,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
}Add CustomChildComponent to your routes in your app.routes.ts:
import { Routes } from '@angular/router';
import { CustomChildComponent } from './custom-child.component';
export const routes: Routes = [
{ path: 'your-path', component: CustomChildComponent },
];Register the component with Builder, specifying that it can accept children:
import {
Builder,
withChildren
} from '@builder.io/react';
import type { PropsWithChildren } from 'react';
// Define the component
const CustomHero = (props: PropsWithChildren) => {
return (
<>
<div>This is your component text</div>
{props.children}
</>
);
};
// IMPORTANT: withChildren is required to enable child block functionality
const HeroWithBuilderChildren = withChildren(CustomHero);
// Register with Builder
Builder.registerComponent(HeroWithBuilderChildren, {
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
});
export default CustomHero;Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
Builder.registerComponent(..., {
name: 'Your-custom-component',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
})Place the childRequirements object inside registerComponent() after defaultChildren. With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
Register the component with Builder, specifying that it can accept children:
import { RegisteredComponent } from '@builder.io/sdk-react';
import { ReactNode } from 'react';
interface CustomHeroProps {
children: ReactNode;
}
const CustomHero = (props: CustomHeroProps) => {
return (
<>
<div>This is text from your component</div>
{props.children}
</>
);
};
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
inputs: [],
canHaveChildren: true,
defaultChildren: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is Builder text',
},
},
},
],
};Register your component with Builder to define how it appears and behaves in the Visual Editor. The registration configuration:
- Sets the component's name and input fields
- Enables child blocks with
canHaveChildren - Provides default content with
defaultChildren - Makes the component available in the Visual Editor's Insert tab
Setting child requirements
To allow only certain types of children, configure childRequirements:
export const customHeroInfo: RegisteredComponent = {
component: CustomHero,
name: 'CustomHero',
...
childRequirements: {
message: 'You can only put Buttons, Text, or Images in a Hero',
query: {
'component.name': { $in: ['Button', 'Text', 'Image'] },
},
}
};With this configuration, the custom component only accepts Button, Text, or Image blocks.
To test your component, visit any Page in your web browser that renders this component.
// src/CodeBlockComponent.jsx
import * as React from "react";
import SyntaxHighlighter from "react-syntax-highlighter";
import { Builder } from "@builder.io/react";
export const CodeBlockComponent = (props) => (
<SyntaxHighlighter language={props.language}>
{props.code}
</SyntaxHighlighter>
);
Builder.registerComponent(CodeBlockComponent, {
name: "Code Block",
inputs: [
{
name: "code",
type: "string",
defaultValue: "const incr = num => num + 1",
},
{
name: "language",
type: "string",
defaultValue: "javascript",
},
],
});
The following video demonstrates adding children to custom components using a basic hero banner.
Single editable regions work well for basic components. However, more complex layouts often require multiple distinct areas for content. The next example covers how to create a two-column layout with separate editable regions.
A common need is having multiple distinct areas within a custom component where users can add content. This section covers an example shows how to create a two-column layout where each column is a separate editable region.
This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses BuilderBlocks for each column, which requires these props:
parentElementIdto identify which Builder block owns the contentdataPathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
// Define the component
type BuilderProps = {
column1: BuilderElement[];
column2: BuilderElement[];
builderBlock: BuilderElement;
};
const CustomColumns = (props: BuilderProps) => {
return (
<>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column1.blocks`}
blocks={props.column1}
/>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column2.blocks`}
blocks={props.column2}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomColumns, {
name: 'MyColumns',
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: {
blocks: [],
},
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: {
blocks: [],
},
},
],
});
export default CustomColumns;This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
interface CustomColumnsProps {
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}
const CustomColumns = (props: CustomColumnsProps) => {
return (
<>
<Blocks
blocks={props.column1}
path="column1"
parent={props.builderBlock.id}
/>
<Blocks
blocks={props.column2}
path="column2"
parent={props.builderBlock.id}
/>
</>
);
};
export const customColumnsInfo: RegisteredComponent = {
name: 'MyColumns',
component: CustomColumns,
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
};This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses BuilderBlocks for each column, which requires these props:
parentElementIdto identify which Builder block owns the contentdataPathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import { BuilderBlocks, type BuilderElement } from '@builder.io/react';
type BuilderProps = {
column1: BuilderElement[];
column2: BuilderElement[];
builderBlock: BuilderElement;
};
const CustomColumns = (props: BuilderProps) => {
return (
<>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column1.blocks`}
blocks={props.column1}
/>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column2.blocks`}
blocks={props.column2}
/>
</>
);
};
export default CustomColumns;This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
interface CustomColumnsProps {
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}
const CustomColumns = (props: CustomColumnsProps) => {
return (
<>
<Blocks
blocks={props.column1}
path="column1"
parent={props.builderBlock.id}
/>
<Blocks
blocks={props.column2}
path="column2"
parent={props.builderBlock.id}
/>
</>
);
};
export const customColumnsInfo: RegisteredComponent = {
name: 'MyColumns',
component: CustomColumns,
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
};This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses BuilderBlocks for each column, which requires these props:
parentElementIdto identify which Builder block owns the contentdataPathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
// Define the component
type BuilderProps = {
column1: BuilderElement[];
column2: BuilderElement[];
builderBlock: BuilderElement;
};
const CustomColumns = (props: BuilderProps) => {
return (
<>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column1.blocks`}
blocks={props.column1}
/>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column2.blocks`}
blocks={props.column2}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomColumns, {
name: 'MyColumns',
inputs: [
{
name: 'column1',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: {
blocks: [],
},
},
{
name: 'column2',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: {
blocks: [],
},
},
],
});
export default CustomColumns;Fixed editable regions serve many use cases, but some components need more flexibility. When users need to add an arbitrary number of content areas, dynamic regions provide the solution. The next tabs component demonstrates this pattern.
This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
interface CustomColumnsProps {
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}
const CustomColumns = (props: CustomColumnsProps) => {
return (
<>
<Blocks
blocks={props.column1}
path="column1"
parent={props.builderBlock.id}
/>
<Blocks
blocks={props.column2}
path="column2"
parent={props.builderBlock.id}
/>
</>
);
};
export const customColumnsInfo: RegisteredComponent = {
name: 'MyColumns',
component: CustomColumns,
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
};This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
interface CustomColumnsProps {
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}
const CustomColumns = (props: CustomColumnsProps) => {
return (
<>
<Blocks
blocks={props.column1}
path="column1"
parent={props.builderBlock.id}
/>
<Blocks
blocks={props.column2}
path="column2"
parent={props.builderBlock.id}
/>
</>
);
};
export const customColumnsInfo: RegisteredComponent = {
name: 'MyColumns',
component: CustomColumns,
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
};This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses BuilderBlocks for each column, which requires these props:
parentElementIdto identify which Builder block owns the contentdataPathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
// Define the component
type BuilderProps = {
column1: BuilderElement[];
column2: BuilderElement[];
builderBlock: BuilderElement;
};
const CustomColumns = (props: BuilderProps) => {
return (
<>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column1.blocks`}
blocks={props.column1}
/>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column2.blocks`}
blocks={props.column2}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomColumns, {
name: 'MyColumns',
inputs: [
{
name: 'column1',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: {
blocks: [],
},
},
{
name: 'column2',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: {
blocks: [],
},
},
],
});
export default CustomColumns;This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
interface CustomColumnsProps {
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}
const CustomColumns = (props: CustomColumnsProps) => {
return (
<>
<Blocks
blocks={props.column1}
path="column1"
parent={props.builderBlock.id}
/>
<Blocks
blocks={props.column2}
path="column2"
parent={props.builderBlock.id}
/>
</>
);
};
export const customColumnsInfo: RegisteredComponent = {
name: 'MyColumns',
component: CustomColumns,
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
};This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses BuilderBlocks for each column, which requires these props:
parentElementIdto identify which Builder block owns the contentdataPathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
// Define the component
type BuilderProps = {
column1: BuilderElement[];
column2: BuilderElement[];
builderBlock: BuilderElement;
};
const CustomColumns = (props: BuilderProps) => {
return (
<>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column1.blocks`}
blocks={props.column1}
/>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column2.blocks`}
blocks={props.column2}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomColumns, {
name: 'MyColumns',
inputs: [
{
name: 'column1',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: {
blocks: [],
},
},
{
name: 'column2',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: {
blocks: [],
},
},
],
});
export default CustomColumns;This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
<script lang="ts">
import { Blocks, type BuilderBlock } from '@builder.io/sdk-svelte';
export let column1: BuilderBlock[];
export let column2: BuilderBlock[];
export let builderBlock: BuilderBlock;
</script>
<!-- Left Column -->
<Blocks parent={builderBlock.id} path="column1" blocks={column1} />
<!-- Right Column -->
<Blocks parent={builderBlock.id} path="column2" blocks={column2} />import type { RegisteredComponent } from '@builder.io/sdk-svelte';
import CustomColumns from './CustomColumns.svelte';
export const CustomColumnsInfo: RegisteredComponent = {
component: CustomColumns,
name: 'MyColumns', // you can define your custom name for the component
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default CustomColumnsInfo;This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
<script lang="ts">
import { Blocks, type BuilderBlock } from '@builder.io/sdk-svelte';
export let column1: BuilderBlock[];
export let column2: BuilderBlock[];
export let builderBlock: BuilderBlock;
</script>
<!-- Left Column -->
<Blocks parent={builderBlock.id} path="column1" blocks={column1} />
<!-- Right Column -->
<Blocks parent={builderBlock.id} path="column2" blocks={column2} />import type { RegisteredComponent } from '@builder.io/sdk-svelte';
import CustomColumns from './CustomColumns.svelte';
export const CustomColumnsInfo: RegisteredComponent = {
component: CustomColumns,
name: 'MyColumns', // you can define your custom name for the component
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default CustomColumnsInfo;Sometimes you need components with multiple editable regions, each with different purposes or restrictions. Here's an example of a two-column layout component with several advanced features:
- Multiple editable regions — left and right columns
- Component restrictions — left column only allows Text and Image blocks
- Configurable column widths
- Custom styling and layout options
The component uses <Blocks> for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
<template>
<!-- Left Column -->
<Blocks :parent="builderBlock.id" :path="'column1'" :blocks="column1" />
<!-- Right Column -->
<Blocks :parent="builderBlock.id" :path="'column2'" :blocks="column2" />
</template>
<script setup lang="ts">
import { Blocks, type BuilderBlock } from '@builder.io/sdk-vue';
const { column1, column2, builderBlock } = defineProps<{
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}>();
</script>Register the component with Builder:
import CustomColumns from '@/components/custom-components/custom-columns/CustomColumns.vue';
import type { RegisteredComponent } from '@builder.io/sdk-vue';
export const CustomColumnsInfo: RegisteredComponent = {
component: CustomColumns,
name: 'MyColumns', // you can define your custom name for the component
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default CustomColumnsInfo;Sometimes you need components with multiple editable regions, each with different purposes or restrictions. Here's an example of a two-column layout component with several advanced features:
- Multiple editable regions — left and right columns
- Component restrictions — left column only allows Text and Image blocks
- Configurable column widths
- Custom styling and layout options
The component uses <Blocks> for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
<template>
<!-- Left Column -->
<Blocks :parent="builderBlock.id" :path="'column1'" :blocks="column1" />
<!-- Right Column -->
<Blocks :parent="builderBlock.id" :path="'column2'" :blocks="column2" />
</template>
<script setup lang="ts">
import { Blocks, type BuilderBlock } from '@builder.io/sdk-vue';
const { column1, column2, builderBlock } = defineProps<{
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}>();
</script>Register the component with Builder:
import type { RegisteredComponent } from '@builder.io/sdk-vue';
import CustomColumns from './CustomColumns.vue';
export const CustomColumnsInfo: RegisteredComponent = {
component: CustomColumns,
name: 'MyColumns', // you can define your custom name for the component
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default CustomColumnsInfo;The component uses the Blocks component for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain only Text and Image blocks
- A right column that can contain any Builder blocks
import { component$ } from '@builder.io/qwik';
import {
Blocks,
type BuilderBlock,
type RegisteredComponent,
} from '@builder.io/sdk-qwik';
export const CustomColumns = component$(
(props: {
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}) => {
return (
<>
<Blocks
blocks={props.column1}
path="column1"
parent={props.builderBlock.id}
/>
<Blocks
blocks={props.column2}
path="column2"
parent={props.builderBlock.id}
/>
</>
);
}
);
export const customColumnsInfo: RegisteredComponent = {
name: 'MyColumns',
component: CustomColumns,
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
};The component uses Builder's Blocks component for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import type {
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-angular';
import { Blocks } from '@builder.io/sdk-angular';
import type {
BuilderContextInterface,
RegisteredComponents,
} from '@builder.io/sdk-angular/lib/node/context/types';
@Component({
selector: 'app-custom-columns',
standalone: true,
imports: [CommonModule, Blocks],
template: `
<h1 style="text-align: center">
Two-column layout using custom components with two editable regions
</h1>
<div
style="display: flex; flex-direction: row; justify-content: space-around; border: 10px solid #ccc; padding: 10px"
>
<blocks
[blocks]="columns[0].blocks"
[path]="'component.options.columns.0.blocks'"
[parent]="builderBlock.id"
[context]="builderContext"
[registeredComponents]="builderComponents"
></blocks>
<blocks
[blocks]="columns[1].blocks"
[path]="'component.options.columns.1.blocks'"
[parent]="builderBlock.id"
[context]="builderContext"
[registeredComponents]="builderComponents"
></blocks>
</div>
`,
})
export class CustomColumnsComponent {
@Input() builderBlock!: BuilderBlock;
@Input() columns: { blocks: BuilderBlock[] }[] = [];
@Input() builderComponents: RegisteredComponents = {};
@Input() builderContext!: BuilderContextInterface;
}
export const customColumnsInfo: RegisteredComponent = {
component: CustomColumnsComponent,
name: 'MyColumns',
inputs: [
{
name: 'columns',
type: 'array',
broadcast: true,
hideFromUI: true,
defaultValue: [
{
blocks: [],
},
{ blocks: [] },
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
builderComponents: true,
builderContext: true,
},
};
Add EditableRegionsComponent to your routes in app.routes.ts:
import { Routes } from '@angular/router';
import { EditableRegionComponent } from './editable-region.component';
export const routes: Routes = [
{
path: 'your-path',
component: EditableRegionComponent
}];The following video demonstrates adding two editable regions by creating two column layout :
The component uses Builder's Blocks component for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import type {
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-angular';
import { Blocks } from '@builder.io/sdk-angular';
import type {
BuilderContextInterface,
RegisteredComponents,
} from '@builder.io/sdk-angular/lib/node/context/types';
@Component({
selector: 'app-custom-columns',
standalone: true,
imports: [CommonModule, Blocks],
template: `
<h1 style="text-align: center">
Two-column layout using custom components with two editable regions
</h1>
<div
style="display: flex; flex-direction: row; justify-content: space-around; border: 10px solid #ccc; padding: 10px"
>
<blocks
[blocks]="columns[0].blocks"
[path]="'component.options.columns.0.blocks'"
[parent]="builderBlock.id"
[context]="builderContext"
[registeredComponents]="builderComponents"
></blocks>
<blocks
[blocks]="columns[1].blocks"
[path]="'component.options.columns.1.blocks'"
[parent]="builderBlock.id"
[context]="builderContext"
[registeredComponents]="builderComponents"
></blocks>
</div>
`,
})
export class CustomColumnsComponent {
@Input() builderBlock!: BuilderBlock;
@Input() columns: { blocks: BuilderBlock[] }[] = [];
@Input() builderComponents: RegisteredComponents = {};
@Input() builderContext!: BuilderContextInterface;
}
export const customColumnsInfo: RegisteredComponent = {
component: CustomColumnsComponent,
name: 'MyColumns',
inputs: [
{
name: 'columns',
type: 'array',
broadcast: true,
hideFromUI: true,
defaultValue: [
{
blocks: [],
},
{ blocks: [] },
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
builderComponents: true,
builderContext: true,
},
};
Add EditableRegionsComponent to your routes in app.routes.ts:
import { Routes } from '@angular/router';
import { EditableRegionComponent } from './editable-region.component';
export const routes: Routes = [
{
path: 'your-path',
component: EditableRegionComponent
}];The following video demonstrates adding two editable regions by creating two column layout :
This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses BuilderBlocks for each column, which requires these props:
parentElementIdto identify which Builder block owns the contentdataPathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import { BuilderBlocks, type BuilderElement } from '@builder.io/react';
type BuilderProps = {
column1: BuilderElement[];
column2: BuilderElement[];
builderBlock: BuilderElement;
};
const CustomColumns = (props: BuilderProps) => {
return (
<>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column1.blocks`}
blocks={props.column1}
/>
<BuilderBlocks
parentElementId={props.builderBlock.id}
dataPath={`column2.blocks`}
blocks={props.column2}
/>
</>
);
};
export default CustomColumns;This example shows how to create a layout component with two distinct editable regions. Users can add different content to each column independently in the Visual Editor.
The component uses Blocks for each column, which requires these props:
parentto identify which Builder block owns the contentpathto store each column's content in the correct locationblocksto manage the actual content for each column
The component registration defines two editable regions using uiBlocks:
- A left column that can contain any Builder blocks
- A right column that can contain any Builder blocks
Users can drag and drop different Builder components into each column independently, creating flexible two-column layouts.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
interface CustomColumnsProps {
column1: BuilderBlock[];
column2: BuilderBlock[];
builderBlock: BuilderBlock;
}
const CustomColumns = (props: CustomColumnsProps) => {
return (
<>
<Blocks
blocks={props.column1}
path="column1"
parent={props.builderBlock.id}
/>
<Blocks
blocks={props.column2}
path="column2"
parent={props.builderBlock.id}
/>
</>
);
};
export const customColumnsInfo: RegisteredComponent = {
name: 'MyColumns',
component: CustomColumns,
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'column1',
type: 'uiBlocks',
defaultValue: [],
},
{
name: 'column2',
type: 'uiBlocks',
defaultValue: [],
},
],
};For more complex use cases, you might need multiple editable regions or slots that can be added dynamically. This tabs example demonstrates how to create a component where users can add new tabs, each with its own editable content area.
This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
BuilderBlocks - Uses
parentElementIdto connect the blocks to the parent component - Uses
dataPathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A name field for the tab label
- A blocks field for the tab's content, using the
uiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
import { useState } from 'react';
// Define the component
type TabProps = {
tabList: { tabName: string; blocks: React.ReactNode[] }[];
builderBlock: BuilderElement;
};
const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<BuilderBlocks
parentElementId={builderBlock?.id}
dataPath={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomTabs, {
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'array',
defaultValue: [],
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [],
},
],
},
],
});
export default CustomTabs;This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
import { useState } from 'react';
interface TabProps {
tabList?: { tabName: string; blocks: BuilderBlock[] }[];
builderBlock: BuilderBlock;
}
export const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<Blocks
parent={builderBlock?.id}
path={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
};This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
BuilderBlocks - Uses
parentElementIdto connect the blocks to the parent component - Uses
dataPathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A name field for the tab label
- A blocks field for the tab's content, using the
uiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
import { useState } from 'react';
// Define the component
type TabProps = {
tabList: { tabName: string; blocks: React.ReactNode[] }[];
builderBlock: BuilderElement;
};
const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<BuilderBlocks
parentElementId={builderBlock?.id}
dataPath={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomTabs, {
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'array',
defaultValue: [],
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [],
},
],
},
],
});
export default CustomTabs;This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
import { useState } from 'react';
interface TabProps {
tabList?: { tabName: string; blocks: BuilderBlock[] }[];
builderBlock: BuilderBlock;
}
export const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<Blocks
parent={builderBlock?.id}
path={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
};This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
BuilderBlocks - Uses
parentElementIdto connect the blocks to the parent component - Uses
dataPathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A name field for the tab label
- A blocks field for the tab's content, using the
uiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
import { useState } from 'react';
// Define the component
type TabProps = {
tabList: { tabName: string; blocks: React.ReactNode[] }[];
builderBlock: BuilderElement;
};
const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<BuilderBlocks
parentElementId={builderBlock?.id}
dataPath={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomTabs, {
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'array',
defaultValue: [],
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [],
},
],
},
],
});
export default CustomTabs;This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
import { useState } from 'react';
interface TabProps {
tabList?: { tabName: string; blocks: BuilderBlock[] }[];
builderBlock: BuilderBlock;
}
export const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<Blocks
parent={builderBlock?.id}
path={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
};This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
import { useState } from 'react';
interface TabProps {
tabList?: { tabName: string; blocks: BuilderBlock[] }[];
builderBlock: BuilderBlock;
}
export const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<Blocks
parent={builderBlock?.id}
path={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
};This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
BuilderBlocks - Uses
parentElementIdto connect the blocks to the parent component - Uses
dataPathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A name field for the tab label
- A blocks field for the tab's content, using the
uiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
import { useState } from 'react';
// Define the component
type TabProps = {
tabList: { tabName: string; blocks: React.ReactNode[] }[];
builderBlock: BuilderElement;
};
const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<BuilderBlocks
parentElementId={builderBlock?.id}
dataPath={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomTabs, {
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'array',
defaultValue: [],
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [],
},
],
},
],
});
export default CustomTabs;This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
import { useState } from 'react';
interface TabProps {
tabList?: { tabName: string; blocks: BuilderBlock[] }[];
builderBlock: BuilderBlock;
}
export const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<Blocks
parent={builderBlock?.id}
path={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
};This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
BuilderBlocks - Uses
parentElementIdto connect the blocks to the parent component - Uses
dataPathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A name field for the tab label
- A blocks field for the tab's content, using the
uiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
import { useState } from 'react';
// Define the component
type TabProps = {
tabList: { tabName: string; blocks: React.ReactNode[] }[];
builderBlock: BuilderElement;
};
const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<BuilderBlocks
parentElementId={builderBlock?.id}
dataPath={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomTabs, {
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'array',
defaultValue: [],
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [],
},
],
},
],
});
export default CustomTabs;This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
<script lang="ts">
import { Blocks, type BuilderBlock } from '@builder.io/sdk-svelte';
export let builderBlock: BuilderBlock;
export let tabList: Array<{ tabName: string; blocks: BuilderBlock[] }>;
let activeTab = 0;
</script>
{#if tabList.length}
<div class="dynamics-slots">
{#each tabList as tab, index}
<button on:click={() => (activeTab = index)}>
{tab.tabName}
</button>
{/each}
<Blocks
blocks={tabList[activeTab].blocks}
path={`tabList.${activeTab}.blocks`}
parent={builderBlock.id}
/>
</div>
{/if}import type { RegisteredComponent } from '@builder.io/sdk-svelte';
import CustomTabs from './CustomTabs.svelte';
export const CustomTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default CustomTabsInfo;This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
<script lang="ts">
import { Blocks, type BuilderBlock } from '@builder.io/sdk-svelte';
export let builderBlock: BuilderBlock;
export let tabList: Array<{ tabName: string; blocks: BuilderBlock[] }>;
let activeTab = 0;
</script>
{#if tabList.length}
<div class="dynamics-slots">
{#each tabList as tab, index}
<button on:click={() => (activeTab = index)}>
{tab.tabName}
</button>
{/each}
<Blocks
blocks={tabList[activeTab].blocks}
path={`tabList.${activeTab}.blocks`}
parent={builderBlock.id}
/>
</div>
{/if}import type { RegisteredComponent } from '@builder.io/sdk-svelte';
import CustomTabs from './CustomTabs.svelte';
export const CustomTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default CustomTabsInfo;Here's a more complex example showing how to build a tabs component where both the tab labels and content are editable.
The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
<Blocks> - Uses Vue's reactive state to manage the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
<template>
<div class="dynamics-slots" v-if="tabList.length">
<button
v-for="(tab, index) in tabList"
:key="index"
:class="{ active: activeTab === index }"
@click="activeTab = index"
>
{{ tab.tabName }}
</button>
<Blocks
:blocks="tabList[activeTab].blocks"
:path="`tabList.${activeTab}.blocks`"
:parent="builderBlock.id"
/>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps } from 'vue';
import { Blocks, type BuilderBlock } from '@builder.io/sdk-vue';
const activeTab = ref(0);
const { builderBlock, tabList } = defineProps<{
builderBlock: BuilderBlock;
tabList: Array<{
tabName: string;
blocks: BuilderBlock[];
}>;
}>();
</script>Register the component with Builder:
import type { RegisteredComponent } from '@builder.io/sdk-vue';
import CustomTabs from './CustomTabs.vue';
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default customTabsInfo;Here's a more complex example showing how to build a tabs component where both the tab labels and content are editable.
The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
<Blocks> - Uses Vue's reactive state to manage the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
<template>
<div class="dynamics-slots" v-if="tabList.length">
<button
v-for="(tab, index) in tabList"
:key="index"
:class="{ active: activeTab === index }"
@click="activeTab = index"
>
{{ tab.tabName }}
</button>
<Blocks
:blocks="tabList[activeTab].blocks"
:path="`tabList.${activeTab}.blocks`"
:parent="builderBlock.id"
/>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps } from 'vue';
import { Blocks, type BuilderBlock } from '@builder.io/sdk-vue';
const activeTab = ref(0);
const { builderBlock, tabList } = defineProps<{
builderBlock: BuilderBlock;
tabList: Array<{
tabName: string;
blocks: BuilderBlock[];
}>;
}>();
</script>Register the component with Builder:
import type { RegisteredComponent } from '@builder.io/sdk-vue';
import CustomTabs from './CustomTabs.vue';
export const CustomTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
},
};
export default CustomTabsInfo;The CustomTabs component renders a tab-based layout with editable regions within each tab. The <Blocks> component renders the child blocks for the active tab and facilitates the editing and customization of those blocks within the Visual Editor.
import { component$, useSignal } from '@builder.io/qwik';
import {
Blocks,
type BuilderBlock,
type RegisteredComponent,
} from '@builder.io/sdk-qwik';
interface TabProps {
tabList: Array<{ tabName: string; blocks: BuilderBlock[] }>;
builderBlock: BuilderBlock;
}
export const CustomTabs = component$((props: TabProps) => {
const activeTab = useSignal(0);
return (
<div class="dynamic-slots">
{props.tabList.map((tab, index) => (
<button
key={index}
class={`tab-button ${activeTab.value === index ? 'active' : ''}`}
onClick$={() => (activeTab.value = index)}
>
{tab.tabName}
</button>
))}
<Blocks
parent={props.builderBlock?.id}
path={`tabList.${activeTab.value}.blocks`}
blocks={props.tabList[activeTab.value].blocks}
/>
</div>
);
});
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
};The component:
- Creates a tab navigation interface using Angular's
*ngFordirective - Provides an editable region within each tab using the
Blockscomponent - Uses Angular's property binding to manage the active tab state
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import type {
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-angular';
import { Blocks } from '@builder.io/sdk-angular';
import type {
BuilderContextInterface,
RegisteredComponents,
} from '@builder.io/sdk-angular/lib/node/context/types';
@Component({
selector: 'app-custom-tabs',
standalone: true,
imports: [CommonModule, Blocks],
template: `
<div>
<h2>Custom Component with editable regions</h2>
<div>
<button
*ngFor="let tab of tabList; let i = index"
[class.active]="activeTab === i"
(click)="activeTab = i" >
{{ tab.tabName }}
</button>
</div>
<div *ngIf="tabList?.length">
<div *ngFor="let tab of tabList; let i = index">
<div [style.display]="activeTab === i ? 'block' : 'none'">
<blocks
[blocks]="tabList[i].children"
[path]="'component.options.tabList.' + i + '.children'"
[parent]="builderBlock.id"
[context]="builderContext"
[registeredComponents]="builderComponents"
></blocks>
</div>
</div>
</div>
</div>
`,
styles: [
`
.active {
background-color: #e0e0e0;
font-weight: bold;
}
`,
],
})
export class CustomTabsComponent {
@Input() builderBlock!: BuilderBlock;
@Input() tabList: { tabName: string; children: BuilderBlock[] }[] = [];
@Input() builderComponents: RegisteredComponents = {};
@Input() builderContext!: BuilderContextInterface;
activeTab = 0;
}
export const customTabsInfo: RegisteredComponent = {
component: CustomTabsComponent,
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is editable block within the builder editor',
},
},
responsiveStyles: {
large: {
display: 'flex',
flexDirection: 'column',
position: 'relative',
flexShrink: '0',
boxSizing: 'border-box',
marginTop: '8px',
lineHeight: 'normal',
height: '200px',
textAlign: 'left',
minHeight: '200px',
},
small: {
height: '200px',
},
},
},
],
},
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
builderComponents: true,
builderContext: true,
},
};Add AdancedChildComponent to your routes in app.routes.ts:
import { Routes } from '@angular/router';
import { AdvancedChildComponent } from './advanced-child.component';
export const routes: Routes = [
{
path: 'your-path',
component: AdvancedChildComponent
}];The following video demonstrates adding advanced sub-components with editable regions:
The component:
- Creates a tab navigation interface using Angular's
*ngFordirective - Provides an editable region within each tab using the
Blockscomponent - Uses Angular's property binding to manage the active tab state
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import type {
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-angular';
import { Blocks } from '@builder.io/sdk-angular';
import type {
BuilderContextInterface,
RegisteredComponents,
} from '@builder.io/sdk-angular/lib/node/context/types';
@Component({
selector: 'app-custom-tabs',
standalone: true,
imports: [CommonModule, Blocks],
template: `
<div>
<h2>Custom Component with editable regions</h2>
<div>
<button
*ngFor="let tab of tabList; let i = index"
[class.active]="activeTab === i"
(click)="activeTab = i" >
{{ tab.tabName }}
</button>
</div>
<div *ngIf="tabList?.length">
<div *ngFor="let tab of tabList; let i = index">
<div [style.display]="activeTab === i ? 'block' : 'none'">
<blocks
[blocks]="tabList[i].children"
[path]="'component.options.tabList.' + i + '.children'"
[parent]="builderBlock.id"
[context]="builderContext"
[registeredComponents]="builderComponents"
></blocks>
</div>
</div>
</div>
</div>
`,
styles: [
`
.active {
background-color: #e0e0e0;
font-weight: bold;
}
`,
],
})
export class CustomTabsComponent {
@Input() builderBlock!: BuilderBlock;
@Input() tabList: { tabName: string; children: BuilderBlock[] }[] = [];
@Input() builderComponents: RegisteredComponents = {};
@Input() builderContext!: BuilderContextInterface;
activeTab = 0;
}
export const customTabsInfo: RegisteredComponent = {
component: CustomTabsComponent,
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [
{
'@type': '@builder.io/sdk:Element',
component: {
name: 'Text',
options: {
text: 'This is editable block within the builder editor',
},
},
responsiveStyles: {
large: {
display: 'flex',
flexDirection: 'column',
position: 'relative',
flexShrink: '0',
boxSizing: 'border-box',
marginTop: '8px',
lineHeight: 'normal',
height: '200px',
textAlign: 'left',
minHeight: '200px',
},
small: {
height: '200px',
},
},
},
],
},
],
},
],
shouldReceiveBuilderProps: {
builderBlock: true,
builderComponents: true,
builderContext: true,
},
};Add AdancedChildComponent to your routes in app.routes.ts:
import { Routes } from '@angular/router';
import { AdvancedChildComponent } from './advanced-child.component';
export const routes: Routes = [
{
path: 'your-path',
component: AdvancedChildComponent
}];The following video demonstrates adding advanced sub-components with editable regions:
This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
BuilderBlocks - Uses
parentElementIdto connect the blocks to the parent component - Uses
dataPathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A name field for the tab label
- A blocks field for the tab's content, using the
uiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Builder,
BuilderBlocks,
type BuilderElement
} from '@builder.io/react';
import { useState } from 'react';
// Define the component
type TabProps = {
tabList: { tabName: string; blocks: React.ReactNode[] }[];
builderBlock: BuilderElement;
};
const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<BuilderBlocks
parentElementId={builderBlock?.id}
dataPath={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
// Register with Builder
Builder.registerComponent(CustomTabs, {
name: 'TabFields',
inputs: [
{
name: 'tabList',
type: 'array',
defaultValue: [],
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
hideFromUI: true,
defaultValue: [],
},
],
},
],
});
export default CustomTabs;This example shows how to create a tabs component where users can add new tabs dynamically and customize the content of each tab. The component:
- Creates a tab navigation interface where users can switch between tabs
- Provides an editable region within each tab using
Blocks - Uses
parentto connect the blocks to the parent component - Uses
pathto specify where the tab's content is stored - Uses
blocksto contain the actual content for the active tab
The component registration defines an array input for the tabs, where each tab includes:
- A
namefield for the tab label - A
blocksfield for the tab's content, using theuiBlockstype
Users can add new tabs and customize their content directly in the Visual Editor.
import {
Blocks,
BuilderBlock,
RegisteredComponent,
} from '@builder.io/sdk-react';
import { useState } from 'react';
interface TabProps {
tabList?: { tabName: string; blocks: BuilderBlock[] }[];
builderBlock: BuilderBlock;
}
export const CustomTabs = ({ tabList, builderBlock }: TabProps) => {
const [activeTab, setActiveTab] = useState(0);
if (!tabList?.length) return null;
return (
<>
<div className="tab-buttons">
{tabList.map((tab, index) => (
<button
key={index}
className={`tab-button ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.tabName}
</button>
))}
</div>
<Blocks
parent={builderBlock?.id}
path={`tabList.${activeTab}.blocks`}
blocks={tabList[activeTab].blocks}
/>
</>
);
};
export const customTabsInfo: RegisteredComponent = {
component: CustomTabs,
name: 'TabFields',
shouldReceiveBuilderProps: {
builderBlock: true,
},
inputs: [
{
name: 'tabList',
type: 'list',
subFields: [
{
name: 'tabName',
type: 'string',
},
{
name: 'blocks',
type: 'uiBlocks',
defaultValue: [],
},
],
},
],
};To customize your components even further, leverage Builder's Input Types.
For more details on child-related options when working with child components, visit the canHaveChildren, childRequirements, and defaultChildren sections of the registerComponent() documentation.
For more examples of custom components with multiple sets of children, visit: