Skip to main content

Component shape checklist

Check a component against these rules before you register it, or when you review one a tool generated. Each table lists a rule, what breaks without it, and the page that explains it.

The example is BlogHero in src/components/BlogHero.tsx, with the props title, summary and image, in the group Blog · Components. On the Blog post template it shows the entry AI 101 at /blog/ai-101.

Register atomic and layout components, and keep compounds rare​

Check this before the rest. The other rules are about whether a component works. This one is about whether to register it at all. It's the most expensive one to fix later, because templates that already use the component have to change.

RuleWhat breaks without itWhere it's explained
Register atomic components, which have no slot and render their own content, and layout components, which hold other components through a slot.A component that renders several pieces of content fixes their arrangement in your code. Moving one of them is a code change and a release.From a design to components
Register a compound only when its pieces always appear together, in the same order, and nobody rearranges them. A compound renders several pieces of content in an arrangement fixed in your code.Authors can't rearrange its pieces or reuse one of them on its own. Every change to the arrangement needs a release.From a design to components
Build the bigger thing in Studio, as a section, from the registered pieces.The arrangement is stored in your stack, not your repo, so authors can change it without a code release.Build sections

Why this matters. The blog's author card is a photo and a name inside a link. Registered as one AuthorCard component, it works on day one. But moving the photo below the name is then a code change, a review and a release, because the arrangement is inside the component. Registered as Avatar and Byline and arranged in Studio, the same change is a drag on the canvas. Both look the same on the page, but only the second lets authors make the change.

Register an interactive component whole when built-in components can't build it, such as a carousel, a form or a modal. Expose the props authors need.

Bind​

RuleWhat breaks without itWhere it's explained
Give each prop the type of the content it binds to, such as string for title and imageurl for image.The prop shows Data Mismatch Found in the Settings tab, and Studio passes a fallback value instead of the bound one.Component schema: props, types and defaults
Type an imageurl prop as a string in your component, and bind it to a file field's url.The Data Picker offers only text fields for an imageurl prop. Inside a Repeater, any field can be bound. Bound to the file itself, the component gets its fallback value with no message.Component schema: props, types and defaults
Type a prop object or array when the component needs the whole value.With any other type, Studio unwraps an object that has one key down to that key's value, so the component doesn't get the object.Component schema: props, types and defaults
Render children through a slot prop. Never hardcode child components inside the component.A hardcoded child has no row in Layers and no settings, so authors can't select, configure or bind it.From a design to components

Render​

RuleWhat breaks without itWhere it's explained
Give every prop a defaultValue, and render when a value is empty.A prop with no value and no default gets its type's empty value, such as "" for a string or [] for an array. A newly dropped component can render blank, or throw if your code expects a value.Component schema: props, types and defaults
In a list prop, handle items with missing fields.In the editor, an empty list bound to content arrives as one empty item, [{}], so authors have something to work with. Code that expects every field throws.Render a list
Render string props as children, and don't call string methods on them.When a string prop has a placeholder and no value, it arrives in the editor as a React element that shows the placeholder, and on your live site as null.Component schema: props, types and defaults
Don't throw.The component renders Component Loading Error with the error message, on the canvas and on your live site. The rest of the template still renders.This page

Select​

The recommended shape is wrap: false, with studioAttributes spread on the component's root element. If you leave wrap out, Studio puts a <div> around your component and selects that instead. The <div> sits between a grid or flex parent and your root, so the parent's layout applies to the <div>, not to your component.

RuleWhat breaks without itWhere it's explained
Register with wrap: false.The parent's grid or flex layout applies to Studio's <div>, not to your component.Register a component
With wrap: false, spread studioAttributes on one root element.Studio can't find the component on the canvas. Clicking it selects its parent.Register a component
Make the root a DOM element, or forward the ref to one.studioAttributes carries a ref. If it reaches something that's neither an element nor an object with an element property, the browser console logs "Reference is not a valid object. It should have a element property."Register a component
Take studioAttributes out of the props by name. Don't spread every prop onto the DOM.React warns about an unknown studioAttributes prop on a DOM element.This page
Spread the remaining attributes on the same root. The ID and Custom Attributes you set in the Settings tab arrive as props.Your component drops them without a warning, so the ID and attributes never reach your HTML.This page

The spread is safe on your live site, where studioAttributes carries no selection attributes. Custom Attributes can't be class or style. Use the Design tab for those.

import { registerComponent, type StudioAttributes } from "@contentstack/studio-react";
import type { HTMLAttributes } from "react";

type BlogHeroProps = StudioAttributes &
HTMLAttributes<HTMLElement> & {
title: string;
summary: string;
image: string;
};

export function BlogHero({ studioAttributes, title, summary, image, ...attributes }: BlogHeroProps) {
return (
<section {...attributes} {...studioAttributes}>
<img src={image} alt="" />
<h1>{title}</h1>
<p>{summary}</p>
</section>
);
}

registerComponent({
type: "blog-hero",
displayName: "BlogHero",
component: BlogHero,
wrap: false,
sections: ["Blog · Components"],
props: {
title: { type: "string", defaultValue: "Post title" },
summary: { type: "string", defaultValue: "A short summary of the post." },
image: { type: "imageurl", defaultValue: "/images/blog-hero.jpg" },
},
});

className is one of the attributes, so the spread also applies the Design tab's classes to the root.

Style​

These rules apply only where the Design tab appears: on freeform templates, and on every composition once Enable Freeform Feature is on for the project.

RuleWhat breaks without itWhere it's explained
Apply className to the root element.The Design tab shows "For design changes to take effect, the component must be configured to use the className property." Make your components styleable
Apply each named style group's prop to the element it styles.The same message, naming the group's prop.Make your components styleable
Don't name a style group after a registered prop.The prop's value replaces the group's classes, so that group's edits don't render.Make your components styleable

Edit​

This applies only if authors edit bound values in Visual Editor.

RuleWhat breaks without itWhere it's explained
Turn on edit tags with cslp: { appendTags: true } when you initialize the SDK, and spread each $ prop, such as $title, onto the element that renders that value.Nothing errors. The value renders, but authors can't edit it in Visual Editor.Make bound values editable in Visual Editor

What you see when a component is the wrong shape​

What you seeWhereRule it points to
Data Mismatch Found under a prop. Its tooltip reads "Expected prop type {expectedType} but received {receivedType} in the component registry. Remove the binding and bind with correct data." Settings tabBind: the prop's type doesn't match the bound content.
Data binding type mismatch in component "{component}": Expected prop "{field}" to be {expectedType} but received {receivedType}. Path: {dataPath} Browser console, on your live siteBind: the same mismatch.
"This content may not display correctly in this view. Please contact your development team to update the component so it can support this type of content." Inside your component, on the canvas and on your live siteBind: a string prop is bound to a group, a reference or a list.
"No properties available for the selected component."Settings tabBind: the registration has no props. The Attributes controls don't show either.
A newly dropped component is blank.CanvasRender: props have no defaultValue.
Component Loading Error followed by an error messageCanvas and your live siteRender: the component threw.
Clicking the component selects its parent.CanvasSelect: wrap: false without the studioAttributes spread.
"Reference is not a valid object. It should have a element property."Browser console, in the editorSelect: the ref reached something that isn't an element.
The ID or a custom attribute is missing from the HTML.Your HTMLSelect: the attributes aren't spread on the root.
"For design changes to take effect, the component must be configured to use the className property." Design tabStyle: className or a group's prop isn't applied.
A bound value can't be edited in Visual Editor.Visual EditorEdit: edit tags are off, or the $ prop isn't spread.

To register a component that passes these rules, see Register a component.

Report a problem with this page