Skip to Content

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:

  1. All components from platform-widgets/ and site-widgets/ are collected via generateFileCollection().
  2. 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.
  3. The matching component is rendered.
  4. In draft/dev mode, the Client<ComponentName> variant is loaded instead to enable live editing in Visor Studio’s Dev Mode.

Example Mapping

Collection nameComponent nameDraft mode component
hero_bannerHeroBannerClientHeroBanner
xtr_cms_widget_headerHeaderClientHeader
content_blockContentBlockClientContentBlock

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:

WidgetDescription
HeaderSite header with navigation.
HeaderNavigationStandalone header navigation bar.
FooterSite footer.
MainNavigationPrimary site navigation.
HeroBannerFull-width hero / banner.
MainBannerSecondary banner variant.
ContentBlockGeneric rich content block.
HeadingStandalone heading element.
CardsCard grid layout.
IconCardsCards with icon decoration.
VerticalCardsVertically stacked card list.
NumberedCardsCards with numbered sequence.
CallToActionCTA button/section.
CalloutHighlighted callout box.
InfobarInformational bar element.
InfopicImage with text info panel.
StatisticsStatistics/numbers display.
ColumnsOfTextMulti-column text layout.
Breadcrumb / BreadcrumbsNavigation breadcrumb trail.
SideMenu / SideNavigationSidebar navigation.
PageNavigationIn-page prev/next navigation.
OnThisPageAnchor-based in-page TOC.
PageListingList of child pages.
PageSummaryPage summary card.
PageTitlePage title element.
ArticleSearchArticle-specific search UI.
ArticleDetailsArticle metadata display.
ArticleHeadingArticle-specific heading.
ArticleBreadcrumbBreadcrumb for article pages.
Search / SiteSearch / SearchEngineSearch UI components.
FacetedSearchFaceted / filtered search UI.
AccordionCollapsible accordion section.
NotificationSite-wide notification bar.
BackToTop / BackToTopButtonScroll-to-top controls.
SgdsMastheadGovernment masthead (SGDS).
OpticalAttributionsOptical 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 nameResolved component
my_widgetMyWidget.tsx
xtr_cms_widget_my_widgetMyWidget.tsx (prefix stripped)
hero_bannerHeroBanner.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.

Last updated on