Create reusable, structured UI blocks#
A component type is a schema for a reusable visual block. It gives editors a focused form while developers control the markup, styling, accessibility, and behavior in code.
Common examples are hero_banner, call_to_action, feature_card, pricing_table, and newsletter_signup.
Content type or component type?#
| Use a content type when… | Use a component type when… |
|---|---|
| The item is managed independently and may appear in many places. | The item is a configurable block in a page or structured content field. |
| It needs its own list/detail delivery request. | It needs a matching frontend component and registry entry. |
| Examples: Article, Product, Author. | Examples: Hero, Button, Feature Grid. |
An Article can relate to an Author content entry. A page can contain a Hero component. This keeps reusable data separate from reusable presentation.
Design one responsibility at a time#
Start with a component that does one clear job. A Hero should present introductory content and perhaps one action. It should not also contain navigation, a product list, and a footer.
Small, focused components are easier for editors to understand and easier for developers to test.
Worked example: Hero Banner#
Create a component type with:
| Setting | Value |
|---|---|
| Name | Hero Banner |
| Code | hero_banner |
| Description | Introductory heading, copy, and primary action. |
Add these fields:
| Name | Code | Type | Required |
|---|---|---|---|
| Eyebrow | eyebrow | Text | No |
| Heading | heading | Text | Yes |
| Description | description | Text Area | No |
| Button label | buttonLabel | Text | No |
| Button URL | buttonUrl | URL | No |
| Image | image | Image | No |
Keep buttonLabel and buttonUrl optional as a pair. The frontend should render a link only when both exist.
Connect the code to React#
The component-type code is the key that connects CMS data to your application. Register the same code in a ComponentRegistry.
import type { ContoprixComponentProps } from "@contoprix/react";
type HeroSettings = {
eyebrow?: string;
heading?: string;
description?: string;
buttonLabel?: string;
buttonUrl?: string;
};
export default function HeroBanner({
settings,
previewAttributes,
}: ContoprixComponentProps) {
const hero = (settings ?? {}) as HeroSettings;
return (
<section {...previewAttributes}>
{hero.eyebrow ? <p>{hero.eyebrow}</p> : null}
<h1>{hero.heading ?? "Untitled section"}</h1>
{hero.description ? <p>{hero.description}</p> : null}
{hero.buttonLabel && hero.buttonUrl ? (
<a href={hero.buttonUrl}>{hero.buttonLabel}</a>
) : null}
</section>
);
}import type { ComponentRegistry } from "@contoprix/react";
import HeroBanner from "@/components/contoprix/HeroBanner";
const components: ComponentRegistry = {
hero_banner: HeroBanner,
};
export default components;For a page-builder component block, Contoprix passes the component fields in settings. It does not pass a Hero's fields as content. content and contents are used for single-content and content-list block data.
Important
Pass previewAttributes to the root element as shown. Visual editing uses these attributes to select and refresh the correct block in the preview iframe.
Use a component field for a fixed nested structure#
A Component field inside a content type or component type points to one component type. Use it when the nested object always has the same shape.
Example: an Article's seo field can use a fixed seo component type with metaTitle, metaDescription, and socialImage fields.
The referenced component type must be selected in the field settings. The CMS validates the link and prevents unsafe circular component nesting.
Use a dynamic zone for approved choices#
A Dynamic Zone field lets editors choose among a known list of component types. Use it for a flexible section of a landing page, not for an unrestricted bucket of arbitrary content.
Example allowed components for pageSections:
hero_banner
feature_grid
testimonial_quote
call_to_actionContoprix requires a Dynamic Zone to have at least one allowed component type. In delivery, each zone item carries the selected component information so the renderer can select the matching registry key.
Render pages with the registry#
When a delivered page has blocks, pass the registry to the SDK renderer:
"use client";
import { PageRenderer } from "@contoprix/react/client";
import type { ContoprixPage } from "@contoprix/types";
import components from "./components";
export function ContoprixRenderer({ page }: { page: ContoprixPage }) {
return <PageRenderer page={page} components={components} />;
}If a component code is absent from the registry, the renderer can show a missing-component placeholder. You can also provide a pulled schema registry for generic component fallback rendering, but custom components remain the right place for production-specific design.
Test a component like an editor would#
Create a test page or dynamic-zone entry and try each case:
- heading only;
- heading plus description;
- a complete button pair;
- button label without URL and URL without label;
- no image and a large image;
- every language that the page supports.
Then preview it and confirm that the frontend does not crash or show empty links. The Visual Builder Setup guide covers the full page-rendering connection, and Visual Editing covers the protected preview flow.