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

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,

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 availble at your app host url, 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 examples calls of registerComponent that showcases 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,
},
}
});

For more detailed examples, 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'
}
});

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

(This section is primarily relevant for Headless API users.)

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.

components/Form.tsx
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.

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

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.

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?

Give feedback on this page