Widgets
What are Widgets?
Widgets are the content building blocks rendered inside layout placeholders. Each widget is a React component that receives content data from Visor and renders a specific UI element — a hero banner, navigation, footer, content block, cards, and so on.
Widgets are organized into two layers:
- Platform widgets (
components/widgets/platform-widgets/) — shipped with the template, available to all implementations. - Site widgets (
components/widgets/site-widgets/) — project-specific custom widgets added per implementation.
How Widget Resolution Works
components/Widget.tsx resolves a widget component from its Visor collection name using the following process:
- All components from
platform-widgets/andsite-widgets/are collected viagenerateFileCollection(). - The widget’s collection name is transformed into a PascalCase component name:
- Strip the
xtr_cms_widget_prefix if present. - Split by
_and capitalise each segment.
- Strip the
- The matching component is rendered.
- In draft/dev mode, the
Client<ComponentName>variant is loaded instead to enable live editing in Visor Studio’s Dev Mode.
Example Mapping
| Collection name | Component name | Draft mode component |
|---|---|---|
hero_banner | HeroBanner | ClientHeroBanner |
xtr_cms_widget_header | Header | ClientHeader |
content_block | ContentBlock | ClientContentBlock |
If no matching component is found, the UnknownWidget platform component is rendered as a fallback.
Platform Widgets
The following widgets are included out of the box:
| Widget | Description |
|---|---|
Header | Site header with navigation. |
HeaderNavigation | Standalone header navigation bar. |
Footer | Site footer. |
MainNavigation | Primary site navigation. |
HeroBanner | Full-width hero / banner. |
MainBanner | Secondary banner variant. |
ContentBlock | Generic rich content block. |
Heading | Standalone heading element. |
Cards | Card grid layout. |
IconCards | Cards with icon decoration. |
VerticalCards | Vertically stacked card list. |
NumberedCards | Cards with numbered sequence. |
CallToAction | CTA button/section. |
Callout | Highlighted callout box. |
Infobar | Informational bar element. |
Infopic | Image with text info panel. |
Statistics | Statistics/numbers display. |
ColumnsOfText | Multi-column text layout. |
Breadcrumb / Breadcrumbs | Navigation breadcrumb trail. |
SideMenu / SideNavigation | Sidebar navigation. |
PageNavigation | In-page prev/next navigation. |
OnThisPage | Anchor-based in-page TOC. |
PageListing | List of child pages. |
PageSummary | Page summary card. |
PageTitle | Page title element. |
ArticleSearch | Article-specific search UI. |
ArticleDetails | Article metadata display. |
ArticleHeading | Article-specific heading. |
ArticleBreadcrumb | Breadcrumb for article pages. |
Search / SiteSearch / SearchEngine | Search UI components. |
FacetedSearch | Faceted / filtered search UI. |
Accordion | Collapsible accordion section. |
Notification | Site-wide notification bar. |
BackToTop / BackToTopButton | Scroll-to-top controls. |
SgdsMasthead | Government masthead (SGDS). |
OpticalAttributions | Optical platform attributions. |
- HeroBanner.tsx
- Header.tsx
- Footer.tsx
- ContentBlock.tsx
- ...
Adding a New Widget
Create the server component
Add the widget to components/widgets/site-widgets/. The filename without .tsx is the resolved component name — keep it in PascalCase. The naming convention from filename to Visor collection is: PascalCase component → lower_snake_case collection.
// components/widgets/site-widgets/MyWidget.tsx
type MyWidgetProps = {
data: any;
content?: any;
searchParams?: any;
};
const MyWidget = ({ data, content }: MyWidgetProps) => {
// All widget content lives in data.parameters
const { title, description } = data?.parameters || {};
return (
<div className="my-widget">
<h2>{title}</h2>
<p>{description}</p>
</div>
);
};
export default MyWidget;
// Export widget metadata — Visor Studio uses this to register the widget
export const widgetJsonConfiguration = {
collection: 'my_widget',
id: 'my_widget',
item: {
id: 'my_widget',
name: 'My Widget',
icon: 'widgets',
widget_definition: { name: 'my_widget' },
parameters: { title: '', description: '' },
},
};Widget props are populated from the widget configuration saved in Visor Studio. Fields defined in the widget’s configuration form are available under data.parameters.
Create the Client variant for Dev Mode
Create a matching client component at components/widgets/site-widgets/client/ClientMyWidget.tsx. This variant is loaded when draftMode is true, enabling inline editing inside Visor Studio’s Dev Mode interface.
// components/widgets/site-widgets/client/ClientMyWidget.tsx
"use client";
import { useState } from "react";
import FormConfigurationProvider from "@/components/form-configuration/FormConfigurationProvider";
type MyWidgetProps = {
data: any;
draftMode: boolean;
};
export default function ClientMyWidget({ data, draftMode }: MyWidgetProps) {
// ✅ Use ?? for each field individually — handles falsy values like "", 0, false correctly
const [formValue, setFormValue] = useState({
...data.parameters,
title: data.parameters?.title ?? "",
description: data.parameters?.description ?? "",
});
return (
<FormConfigurationProvider data={data} draftMode={draftMode}>
<div className="my-widget">
<h2>{formValue.title}</h2>
<p>{formValue.description}</p>
</div>
</FormConfigurationProvider>
);
}FormConfigurationProvider connects the widget to Visor Studio’s configuration panel, allowing editors to update widget fields without leaving the page.
Always initialise useState using the ?? (nullish coalescing) operator for each field individually. Spreading data.parameters first and then setting defaults does not work — if a saved value is "false", "", or 0, it will be overwritten by the default. The ?? pattern correctly handles all falsy saved values.
Name your collection in Visor Studio
The widget’s collection name in Visor Studio must map to your component filename through PascalCase conversion:
| Visor collection name | Resolved component |
|---|---|
my_widget | MyWidget.tsx |
xtr_cms_widget_my_widget | MyWidget.tsx (prefix stripped) |
hero_banner | HeroBanner.tsx |
Create or update the corresponding widget definition in Visor Studio so editors can add it to pages via the Page Builder.
Avoid duplicate filenames between platform-widgets/ and site-widgets/. When both contain a file of the same name, site-widgets takes precedence and shadows the platform widget.