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:
- The actual React component function or class, so that Plasmic can actually instantiate and render those components
- 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,
import ProductCard from '../components/ProductCard';export const PLASMIC = initPlasmicLoader(...);PLASMIC.registerComponent(ProductCard, {name: "ProductCard",props: {// Pass in arbitrary content in the visual editing canvaschildren: 'slot',// You can have any number of slots.header: 'slot',// Simple scalar propsproductId: 'string',darkMode: 'boolean',// Some props can take an enum of optionselevation: {type: 'choice',options: ['high', 'medium', 'flat']}// Some props can take an arbitrary JSON objectconfig: 'object',// Some props can have dynamic configurationsheaderColor: {type: 'choice',// Hide the 'color' prop if no header contenthidden: (props) => !props.header,// Offer different choices depending on if darkMode is onoptions: (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
import { Slider } from 'antd';// A slider with default stylesregisterComponent(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.
// 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 slotallowedComponents: ['MenuItem'],// Default slot contents: two MenuItemsdefaultValue: [{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:
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 aproductId
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 propshowDescription
(boolean) is enabled. - List props of children: You have an
Accordion
component that takes as childrenAccordionPanel
s, each with anid
prop. It also takes aexpandedId
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 theInput
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 propsmin
andmax
.
You can use “prop control functions” for these and more situations.
Here’s a simple example of conditionally hiding a prop:
function Slider({start, end, hasEnd,}: {hasEnd?: booleanstart?: number,end?: number,}) {return <div>...</div>;}registerComponent(ProductCard, {name: 'Slider',props: {hasEnd: 'boolean'start: 'number',end: {type: 'number',// `hidden` takes a prop control functionhidden: 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:
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,
// Registering Parallax Tilt as an attachmentregisterComponent(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
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',// The section in which the component should be shown in Studio.section: "Example components",// A small image (as a URL) of the component to be shown in Studio.thumbnailUrl: "https://example.com/thumbnail.svg",// 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
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,
// Steps component with two templatesregisterComponent(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.
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.
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.
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!
Have feedback on this page? Let us know on our forum.