Component schema: props, types and defaults
Look up what you can declare in a component's props when you register it: the fields every prop takes, the prop types, each type's options, how defaults work and which props can control design. For the registration call itself, see Register a component.
The example on this page is a team blog. Its Blog post content type is blog_post, and the entry AI 101 lives at /blog/ai-101. BlogHero, in src/components/BlogHero.tsx, has the props title, summary and image. AuthorCard is the blog's compound, with the props name, photo and link. This page keeps it because its three props show three prop types. The Author card section itself is built in Studio from smaller pieces. See From a design to components.
Schema shape
props maps each prop name to its declaration. The name is the prop your component receives.
props: {
title: { type: "string", displayName: "Title", placeholder: "Your post title" },
summary: { type: "string", displayName: "Summary", control: "large" },
image: "imageurl",
}
image: "imageurl" is the short form. It works for a type that needs no options: string, number, boolean, datestring, href, imageurl, array, object and slot. A short array takes items of any type, and a short object has no properties. choice, plaintext, json_rte and any need the full form.
Every prop takes these fields.
| Field | Required | What it does |
|---|---|---|
type | Yes | One of the prop types. |
displayName | No | The label in the Settings tab. |
defaultValue | No | The value a newly dropped component starts with. See Default values. |
helpText | No | Text behind a help icon next to the field in the Settings tab. |
design | No | true makes the prop control a CSS property. See Props that control design. |
Studio ignores any other field, so default: in place of defaultValue: does nothing.
To make a bound value editable in Visual Editor, your component also reads a $ prop for each bound prop. That's in Make bound values editable in Visual Editor.
The prop types
| Type | Your component receives | Control in the Settings tab | Can be bound to |
|---|---|---|---|
string | A string | A text box. control can make it larger or a Markdown editor. | Text fields. Number and boolean fields pass too. |
plaintext | A string | A text box, like string | Single-line and multi-line text, number and boolean fields only. Not rich text, Markdown or multiple fields. |
number | A number | A number box, or a slider | Number fields |
boolean | true or false | A toggle | Boolean fields |
choice | One string, or a list of strings with multiSelect: true | Radio buttons, or a dropdown. With multiSelect: true, checkboxes. | Text fields |
href | A string | A URL box that shows URL is invalid on a bad value. It's not a link picker. | Text fields |
imageurl | A string | Choose a file, Upload a file and Enter URL | Text fields, such as a file field's url |
datestring | A string | A date picker, or a date and time picker | Date fields |
array | A list | An editable list | Multiple-value fields, such as modular blocks. A query result gives its entries. |
object | An object | A nested form | Group and JSON fields |
slot | Rendered React content | A drop zone on the canvas | Nothing. Authors drop components into it. |
json_rte | The JSON RTE document, plus a jsonConfig prop | A JSON editor | JSON RTE fields |
any | Any value | A text box, or a JSON editor for a JSON RTE value | Anything |
There's no separate multi-choice type. It's choice with multiSelect: true.
For any, an object with a single key is unwrapped to its value, and that repeats down the tree. Use any only for plain values, and pick object or array for structured ones.
To render a json_rte value, see Render rich text and embedded entries.
Slot props
A slot prop gives your component a drop zone. Authors drop components into it on the canvas, and your component receives what they dropped, already rendered. BlogHero is the compound the blog keeps on purpose, for its one fixed hero treatment. To give BlogHero a drop zone under the summary, for a share button, declare a slot and render it where the drop zone goes.
props: {
actions: { type: "slot", displayName: "Actions", helpText: "Drop a button here" },
}
<p>{summary}</p>
<div>{actions}</div>
For a slot, helpText is the drop zone's tooltip. A slot can't be bound, and it has no $ prop. To pass data into what's dropped in a slot, see StudioComposition and Slot props.
Give slot data a schema with dataFrom
Data your component passes through <Slot data> reaches Studio as plain JSON. So a Repeater in the slot bound to component_props.<key> has no content type schema, and its conditions can't use Content Type or Block Name. Add dataFrom to the slot to map that key to the prop whose bound data it passes on.
props: {
posts: { type: "array", displayName: "Posts" },
postSlot: { type: "slot", displayName: "Post slot", dataFrom: { items: "posts" } },
}
<Slot data={{ items: posts }}>{postSlot}</Slot>
Studio then reads the schema for component_props.items from whatever posts is bound to. Declare a key only when its value holds the prop's entries unchanged, or sliced, filtered or sorted. dataFrom changes only the schema Studio uses. It doesn't change the data your component renders.
If an entry names a prop that isn't registered, or names another slot, registration still succeeds. The console warns [studio] Ignoring `dataFrom` entry "<key>": ... and the SDK drops that entry.
Type-specific options
| Type | Option | Values | What it does |
|---|---|---|---|
string | control | "default", "large", "markdown" | A text box, a larger text box, or a Markdown editor. The default is "default". |
string | placeholder | A string | Faded text on the canvas while the prop has no value and no default. It shows only in the editor. |
plaintext | control | "default", "large" | A text box, or a larger text box |
plaintext | placeholder | A string | Placeholder text inside the empty text box |
number | control | "default", "slider" | A number box, or a slider |
number | min, max, step | Numbers | The range and step |
choice | options | A list of strings, or of { value, label } | What the author picks from. Values are strings. |
choice | multiSelect | true, false | true lets the author pick more than one. The default is false. |
choice | control | "radio", "dropdown" | Radio buttons, or a dropdown. The default is "radio". |
href | placeholder | A string | Placeholder text inside the empty URL box |
datestring | control | "date", "datetime" | A date, or a date and time. The default is "date". |
array | items | A prop declaration | The type of each item, such as { type: "string" }. Required in the full form. |
object | properties | A props map | The fields inside the object. Required in the full form. |
Default values
Put a default on each prop with defaultValue.
| When | What happens |
|---|---|
| An author drops the component | Studio copies each prop's defaultValue into the template as that prop's value. It's saved with the template and shows on your live site. |
You change a defaultValue in code later | Components already placed keep the value they were dropped with. Only new drops get the new default. |
| A prop has no value | The component gets the defaultValue. With none, a string with a placeholder shows the placeholder in the editor. Otherwise it gets the type's empty value, such as an empty string. |
| A bound field has no value | The component gets the same fallback. A bound field that holds an empty string or null passes that through instead. |
| The prop controls design | The defaultValue becomes the value at the default breakpoint. |
Write choice defaults like this.
| Choice | defaultValue |
|---|---|
| Single-select | One value, such as "left", or a list with one value, such as ["left"] |
| Multi-select | A list, such as ["left", "center"] |
For an array, the defaultValue is the starting list, and each item an author adds in the Settings tab starts with items.defaultValue.
Because defaults ship to your live site, pick ones that show the component clearly when it's dropped and still read as placeholder copy, such as "Your post title" rather than a real headline.
Props that control design
A design prop lets an author set one CSS property on your component, per breakpoint, from the Settings tab. Mark it with design: true. For what authors do with it, see Make your components styleable.
props: {
textAlign: {
type: "choice",
design: true,
displayName: "Alignment",
options: [
{ label: "Left", value: "left" },
{ label: "Center", value: "center" },
],
defaultValue: "left",
},
}
<h1 className={textAlign}>{title}</h1>
| Rule | Detail |
|---|---|
| Only two types qualify | choice, with each option's value a CSS value, and string |
| The prop name is the CSS property | In camelCase, such as textAlign, paddingInline or maxWidth |
| Some names are reserved | default, className, children, style, key, ref, and the name of any style group on the component |
| Your component gets a class name | It receives a generated class under the prop name, never the raw value, and has to put that class on an element |
| The control sits with the other props | It shows in the Settings tab, not the Design tab |
When a design prop breaks a rule, Studio doesn't throw. It drops design: true and logs a warning in the browser console, and the prop becomes an ordinary prop.
[studio] Ignoring `design: true` on prop "text-align" of component "blog-hero": "text-align" is not a camelCase CSS property.
The warning ends with one of these reasons.
| Reason | What's wrong |
|---|---|
type "number" is not supported (allowed: choice, string). | The prop isn't a choice or string. |
"style" is a reserved key. | The prop name is reserved. |
"title" collides with a registered style group. | A style group on the component has the same name. |
"text-align" is not a camelCase CSS property. | The name isn't a CSS property in camelCase. |
A name that has the right shape but that the browser doesn't know stays a design prop, with this warning.
[studio] Design prop "textAling" of component "blog-hero" does not match a known CSS property in this browser — its values will emit a no-op declaration.
A complete worked example
BlogHero and AuthorCard, declared in one call. AuthorCard binds to the author_card global field through the text fields inside it. Bind name to name, photo to photo.url, and link to link.href.
import { registerComponents } from "@contentstack/studio-react";
import { BlogHero } from "../components/BlogHero";
import { AuthorCard } from "../components/AuthorCard";
registerComponents([
{
type: "blog-hero",
component: BlogHero,
displayName: "Blog Hero",
sections: "Blog · Components",
wrap: false,
props: {
title: {
type: "string",
displayName: "Title",
placeholder: "Your post title",
helpText: "Bind this to the post's title field.",
},
summary: {
type: "string",
displayName: "Summary",
control: "large",
defaultValue: "A one-line summary of the post.",
},
image: { type: "imageurl", displayName: "Image" },
textAlign: {
type: "choice",
design: true,
displayName: "Alignment",
options: [
{ label: "Left", value: "left" },
{ label: "Center", value: "center" },
],
defaultValue: "left",
},
},
},
{
type: "AuthorCard",
component: AuthorCard,
displayName: "Author Card",
sections: "Blog · Components",
wrap: false,
props: {
photo: { type: "imageurl", displayName: "Photo" },
name: {
type: "string",
displayName: "Name",
placeholder: "Author name",
helpText: "Used as the photo's alt text.",
},
},
},
]);
Bind an imageurl prop to the file's url
An imageurl prop's value is a string, so type it string in your component. A file field holds an object, so bind the prop to the file's url inside it. 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. The fallback is the static value, then the defaultValue, then "".
If it doesn't work
Data Mismatch Found under a prop
The Settings tab shows Data Mismatch Found under a bound prop, and its help tooltip reads:
Expected prop type string but received object in the component registry. Remove the binding and bind with correct data.
The bound field's value doesn't match the prop's type. Bind a field of the right kind, or change the type. The message goes away once the value matches.
On your live site, the same problem logs a warning in the browser console:
Data binding type mismatch in component "blog-hero": Expected prop "title" to be string but received object. Path: ...
Your component shows "This content may not display correctly"
The component shows this text where the content should be:
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.
A string prop is bound to a group, a reference or a list. Bind a text field inside it, or change the prop's type to object or array.
No properties available for the selected component
The Settings tab shows No properties available for the selected component. The registration has no props. Add them.
A prop has no control in the Settings tab
The type isn't one of the prop types, such as a misspelt "text". The prop can only be bound, and the browser console shows:
[NodePropInput] No renderer for prop type "text" (propKey="title"). Registered types: ... Falling back to bind-only mode.
Change it to a real type, such as "string". TypeScript also flags an unknown type when you build.
A design prop acts like an ordinary prop
The console shows [studio] Ignoring design: true on prop, then the prop and the reason. Fix what the reason names. See Props that control design.
A new default doesn't show on components already placed
Studio copies defaults into the template when a component is dropped. Components placed before your change keep their old value. Change the value on each one in the Settings tab, or drop the component again.