Registering your code components

Once you have your app host page set up, you can now tell Plasmic about the custom components you want to use in the Studio, by registering them.

Component registration

You must provide your registration calls ahead of wherever they will be needed, which includes two places:

  • The <PlasmicHost/> (/plasmic-host) page, for Plasmic Studio.
  • Wherever <PlasmicComponent/> is rendered, for your final pages.

This is why we typically conventionally place them in plasmic-init.ts, and import this into both types of locations.

When you register a React component, you are giving Plasmic two pieces of critical information:

  1. The actual React component function or class, so that Plasmic can actually instantiate and render those components
  2. Component metadata, like its name and its props, so that Plasmic can generate the control UI in the right panel for Plasmic users to configure the component props.

For example,

Typically you should perform your registrations in your `plasmic-init.ts` file.
plasmic-init.ts
Copy
import ProductCard from '../components/ProductCard';
export const PLASMIC = initPlasmicLoader(...);
PLASMIC.registerComponent(ProductCard, {
name: "ProductCard",
props: {
// Pass in arbitrary content in the visual editing canvas
children: 'slot',
// You can have any number of slots.
header: 'slot',
// Simple scalar props
productId: 'string',
darkMode: 'boolean',
// Some props can take an enum of options
elevation: {
type: 'choice',
options: ['high', 'medium', 'flat']
}
// Some props can take an arbitrary JSON object
config: 'object',
// Some props can have dynamic configurations
headerColor: {
type: 'choice',
// Hide the 'color' prop if no header content
hidden: (props) => !props.header,
// Offer different choices depending on if darkMode is on
options: (props) => props.darkMode ? ['black', 'blue'] : ['yellow', 'green']
}
}
});

Visit the Component Registration API page to learn more about all the metadata you can specify when you register your component.

Use your code components in Studio!

Once you have finished registering your code components, make sure your changes are available on your app host, and reload your Plasmic project. You should see your code components now show up on the Components tab, or in the blue plus button menu.

You are now able to add instances of your code components directly to the artboards and configure their props on the right panel — no different from any other component created in Plasmic!

Example component registrations

Here are some more example calls of registerComponent that showcase a few possibilities.

Check out the Component registration API for full details on what you can do!

Specifying default styles

Copy
import { Slider } from 'antd';
// A slider with default styles
registerComponent(Slider, {
name: 'Slider',
props: {
disabled: 'boolean',
vertical: 'boolean'
},
defaultStyles: {
width: '100%',
maxWidth: '180px'
}
});

Specifying default slot contents

When inserting a component that has slots, you can specify full templates of element trees that initially fill those slots.

This helps editors to not start with an empty setup; it’s always easier to start with a template that they can then edit. Without this, you’ll often just see an empty box placeholder on the canvas.

This guidance is especially helpful when you have a “family” of components that are meant to work together, such as a Menu of MenuItems.

We highly recommend always providing default slot contents whenever you have slot props! Even if it’s just a text element, it helps to understand what type of content is even supposed to go into the slot.

See the full element type reference.

Copy
// A pair of components, where Menu's children slot accepts only MenuItems.
registerComponent(MenuItem, {
name: 'MenuItem',
displayName: 'Menu Item',
props: {
key: 'string',
children: 'slot'
}
});
registerComponent(Menu, {
name: 'Menu',
props: {
mode: 'string',
theme: 'string',
selectedKeys: 'object',
defaultSelectedKeys: 'object',
children: {
type: 'slot',
// Only allow MenuItem in children slot
allowedComponents: ['MenuItem'],
// Default slot contents: two MenuItems
defaultValue: [
{
type: 'component',
name: 'MenuItem',
props: {
key: 'key1',
children: {
type: 'text',
value: 'Menu Option 1'
}
}
},
{
type: 'component',
name: 'MenuItem',
props: {
key: 'key2',
children: {
type: 'text',
value: 'Menu Option 2'
}
}
}
]
}
}
});

Allowing only specific elements as slot contents

To help guide editors and enforce correct compositions, you can specify allowedComponents on slots.

So you can dictate, for instance:

  • The Page Layout component should only accept Section components as its children.
  • The CTA slot of Hero Section components should accept only Button components.
  • Menus should only accept MenuItems, or Accordions should only accept AccordionPanels.`

Here is an example of this:

Copy
registerComponent(Hero, {
name: 'Hero',
cta: {
type: 'slot',
allowedComponents: ['Button']
}
});

Dynamic controls

Consider the following scenarios:

  • Have a dynamic dropdown of options: You have a Product code component with a productId prop. You want a dropdown to let the editor choose a product to display, but don’t want a static hardcoded list of options. Instead you want this to be dynamic, showing the current products from your backend data source.
  • Conditionally hide or disable based on other prop: You have a string prop description. However, you normally want to hide this prop, unless another prop showDescription (boolean) is enabled.
  • List props of children: You have an Accordion component that takes as children AccordionPanels, each with an id prop. It also takes a expandedId prop—you want this to show a dropdown of all the children’s IDs.
  • List data communicated from descendants: You have a Form component. You want it to have a dropdown that lists all the Input element names for anything in the component.
  • Dynamic range: You have a Slider component, and want a numeric value prop to have a dynamic min/max based on the values of other props min and max.

You can use “prop control functions” for these and more situations.

Here’s a simple example of conditionally hiding a prop:

Copy
function Slider({
start, end, hasEnd,
}: {
hasEnd?: boolean
start?: number,
end?: number,
}) {
return <div>...</div>;
}
registerComponent(ProductCard, {
name: 'Slider',
props: {
hasEnd: 'boolean'
start: 'number',
end: {
type: 'number',
// `hidden` takes a prop control function
hidden: props => !props.hasEnd,
},
}
});

Here’s an example showing how to dynamically list options for a dropdown, where the choices are some set of product slugs computed by the component instance itself:

Copy
function ProductCard({
productId,
setControlContextData
}: {
productId?: string;
// When rendered in Studio artboard, a `setControlContextData` function will
// be injected as a prop. This function can relay any data you want, so you
// define whatever type it accepts.
setControlContextData?: (ctxData: { allProductSlugs: string[] }) => void;
}) {
const { allProductSlugs } = React.useContext(StoreInfo);
// Tell the prop control about what products are available
// to choose from. We are just calling this for the side effect.
setControlContextData?.({
allProductSlugs
});
return <div>...</div>;
}
registerComponent(ProductCard, {
name: 'ProductCard',
props: {
productId: {
type: 'choice',
options: (props, ctx) => {
// Use the product slugs given to us from setControlContextData()
return ctx.allProductSlugs;
}
}
}
});

For more detailed examples and explanation, see the reference.

Custom behaviors (attachments)

You can register components as “attachments”, which are wrapper components (like animations, effects, behaviors, etc) that can be used to attach various elements. These components will be available on the right panel of Studio, in the Custom behaviors section.

Notice that these components can have any number of props, but can only have a single slot prop called “children”.

For example,

Copy
// Registering Parallax Tilt as an attachment
registerComponent(ReactParallaxTilt, {
name: 'Parallax Tilt',
isAttachment: true,
props: {
tiltEnabled: 'boolean',
tiltReverse: 'boolean',
tiltAngleX: 'number',
tiltAngleY: 'number',
children: 'slot'
}
});

See our custom behaviors page for more information.

Additional prop settings

Copy
registerComponent(MyComponent, {
props: {
isFlippedX: {
type: 'boolean',
// A more human-friendly readable prop name.
displayName: 'Is Flipped X',
// Some helpful descriptive tooltip on the prop.
description: 'Whether the reverse the direction of the animation horizontally',
// Indicates what the default value is if left unspecified or when reset.
// For text inputs, this is a placeholder.
defaultValueHint: true,
// Hide/collapse the prop by default. Intended for advanced or less
// frequently used props, to reduce UI clutter.
advanced: true,
// Hidden altogether, instead of just collapsed.
hidden: true
// This field cannot be edited.
readOnly: true,
}
}
});

Additional component settings

Copy
registerComponent(
MyComponent,
{
props: {}
// Do not allow for styles to be set on this element.
styleSections: false,
// Do not allow for this element to be made to be repeated.
isRepeatable: false,
}
)

Multiple templates

You can specify component templates, which are pre-defined set of props and styles that are used to instantiate your component. Studio user can choose from this list of templates to use. For example,

Copy
// Steps component with two templates
registerComponent(Steps, {
name: 'Steps',
props: {
current: 'number',
direction: {
type: 'choice',
options: ['horizontal', 'vertical']
},
percent: 'number',
size: {
type: 'choice',
options: ['default', 'small']
},
type: {
type: 'choice',
options: ['default', 'navigation']
},
children: {
type: 'slot',
allowedComponents: ['Step']
}
},
importPath: 'antd',
templates: {
Basic: {
previewImg: 'https://static1.plasmic.app/steps_template_basic.png',
props: {
direction: 'vertical',
current: 1,
percent: 60,
children: [
{
type: 'component',
name: 'Step',
props: {
title: 'Finished',
description: 'This is a description'
}
},
{
type: 'component',
name: 'Step',
props: {
title: 'In progress',
subTitle: 'Left 00:01:26'
}
},
{
type: 'component',
name: 'Step',
props: {
title: 'Waiting'
}
}
]
}
},
Navigation: {
previewImg: 'https://static1.plasmic.app/steps_template_navigation.png',
props: {
type: 'navigation',
size: 'small',
current: 1,
children: [
{
type: 'component',
name: 'Step',
props: {
title: 'Step 1',
subTitle: '00:00:05',
status: 'finish'
}
},
{
type: 'component',
name: 'Step',
props: {
title: 'Step 2',
subTitle: '00:01:02',
status: 'process'
}
},
{
type: 'component',
name: 'Step',
props: {
title: 'Step 1',
subTitle: 'waiting',
status: 'wait'
}
}
]
}
}
}
});
registerComponent(Step, {
name: 'Step',
props: {
title: 'string',
description: 'string',
subTitle: 'string',
status: {
type: 'choice',
options: ['wait', 'process', 'finish', 'error']
}
},
importPath: 'rc-steps'
});

Tree shaking and bundle optimization

For Headless API

In our examples so far, we have imported components into plasmic-init (or wherever your PLASMIC object is defined), which in turn is used in all pages that render Plasmic content.

This is fine for many use cases, but it does mean that all components will always be loaded. If you need to optimize your bundle sizes, we recommend colocating your registration calls with the components themselves, and then selectively importing just the components you need into the pages that need them.

For instance, let’s say you have a complex Form component, and you have multiple Plasmic pages including a Contact page. If only Contact needs Form, then you don’t need to import Form into plasmic-init.

Copy
import { PLASMIC } from '../plasmic-init';
export default function Form(props: {}) {
// ...
}
// Co-locate the registration call here, with the component itself.
PLASMIC.registerComponent(Form, {
name: 'Form',
props: {
// ...
}
});

Then you can selectively import that component into just the Contact page. Note that this import statement only specifies the path to the module. This is because we’re making the import only for its side effect. Do not use import Form from '../components/Form' in this case. If you do so then bundlers may optimize the import away.

Copy
import '../components/Form';
export default function ContactPage() {
// Ignoring the other context around this component.
return <PlasmicLoader component="ContactPage" />;
}

Dynamically loading components with code splitting

Additionally, you can use dynamic imports to load components later or on demand. This makes sense to use for larger components, or components that are not used on most (performance-sensitive) pages, or are not part of the initial page load (for instance, are further down the page or only appear later, e.g. in a modal).

For example, if you have a Lottie animation component, then you can use dynamic imports for your particular framework or bundler to load the Lottie component only as needed.

Copy
import dynamic from 'next/dynamic';
const LazyLottie = dynamic(() => import('lottie-react'));
PLASMIC.registerComponent(LazyLottie, {
name: 'Lottie',
props: {
// ...
}
});

For Codegen

If you are using Codegen, we recommend putting your registerComponent() call on the host page, so that only the host page depends on all the components that you are registering. When we generate Plasmic code, we will generate code that imports the code components the “usual” way, depending on what you specified as the importPath, so your bundler should automatically be able to tree-shake it as usual.

Styles

Usually code components assume styles are applied via a className prop.

If this goes by a different name, such as containerClassName, then specify this name via 'classNameProp'. (There should only be one 'classNameProp'.)

Next: Writing code components that work great with Plasmic

Follow this writing code components guide to see how you can make sure your code components work great with Plasmic!

Was this page helpful?

Have feedback on this page? Let us know on our forum.