Priya writes about everyday money management and leads the personal banking product team at Mosey Bank.
Developer Demo
Display Templates
Feature Item#
Three visual layouts for the same title and description. The fallback (no template selected) renders a plain white card.
No template (fallback)
Instant transfers
Move money between Mosey accounts in seconds, any time of day, with no fees on standard transfers.
Outlined card
Instant transfers
Move money between Mosey accounts in seconds, any time of day, with no fees on standard transfers.
Colored card
Instant transfers
Move money between Mosey accounts in seconds, any time of day, with no fees on standard transfers.
Flat (divider only)
Instant transfers
Move money between Mosey accounts in seconds, any time of day, with no fees on standard transfers.
Product Card#
Default and Featured templates share the same settings; Featured adds a highlight ring. Backgrounds come from the shared set.
Call to Action#
Four button treatments for the same label and link, plus a size setting on each.
No template (brand button)
Outlined button
White background button
Text link with arrow
Section Heading#
Left-aligned with an optional accent bar, or centered. Both accept the shared background, heading size, alignment, and font settings.
Default (left-aligned), accent bar
Banking that fits your life
Everything you need to manage money day to day, in one app.
Centered heading, Off-white background
Banking that fits your life
Everything you need to manage money day to day, in one app.
Testimonial#
A boxed card with background choices, or a minimal pull-quote with an accent rail.
Card (boxed), White
“Switching to Mosey Bank took one afternoon. The app does everything my old bank needed a branch visit for.”
Sarah Chen
Small business owner
Card (boxed), bright green
“Switching to Mosey Bank took one afternoon. The app does everything my old bank needed a branch visit for.”
Sarah Chen
Small business owner
Minimal (pull-quote with accent)
“Switching to Mosey Bank took one afternoon. The app does everything my old bank needed a branch visit for.”
Sarah Chen
Small business owner
Stats Counter#
An accent rail with a number color setting, or a highlighted box with layout density. The fallback renders a plain centered stat.
No template (fallback)
2.4M
Active customers
Accent rail, green number
2.4M
Active customers
Accent rail, teal number
2.4M
Active customers
Highlighted, Dark, compact
2.4M
Active customers
Outcome Stat#
Inline places the number and label side by side; Boxed defaults to the bright green fill.
Inline stat
of support chats answered in under a minute
Inline stat, Off-white badge
of support chats answered in under a minute
Boxed stat
of support chats answered in under a minute
Team Member#
Vertical card fallback versus the horizontal card template with a background choice.
No template (vertical card)
Marcus Webb
Chief Technology Officer
Marcus leads the engineering teams behind the Mosey app and open banking platform.
LinkedIn →Horizontal card
Marcus Webb
Chief Technology Officer
Marcus leads the engineering teams behind the Mosey app and open banking platform.
LinkedIn →Horizontal card, green
Marcus Webb
Chief Technology Officer
Marcus leads the engineering teams behind the Mosey app and open banking platform.
LinkedIn →Pricing Tier#
Standard versus the Compact template, which tightens padding and type. Highlighting comes from content; backgrounds from the shared set.
No template (standard)
Everyday
- Free instant transfers
- Round-up savings
- Spending insights
- 24/7 chat support
Standard, highlighted
Everyday
- Free instant transfers
- Round-up savings
- Spending insights
- 24/7 chat support
Compact
Everyday
- Free instant transfers
- Round-up savings
- Spending insights
- 24/7 chat support
Compact, bright green
Everyday
- Free instant transfers
- Round-up savings
- Spending insights
- 24/7 chat support
FAQ Item#
Boxed card fallback versus the minimal divider-only template. Click a question to expand it.
No template (boxed)
How long does it take to open an account?
Minimal (divider only)
How long does it take to open an account?
Text Block#
The Narrow layout template constrains line length and adds text size, alignment, font, and vertical padding settings.
No template (standard width)
Display templates separate what content says from how it looks. The editor picks a template and settings in Visual Builder; the component maps those choices to Tailwind classes at render time.
Narrow layout, large text, compact padding
Display templates separate what content says from how it looks. The editor picks a template and settings in Visual Builder; the component maps those choices to Tailwind classes at render time.
Image#
The Rounded corners template adds a radius and an aspect ratio setting that crops the image to a fixed shape.
No template (natural size)

Rounded corners, 1:1 Square

Logo Grid#
Logo size, partner names, and alignment settings. Placeholder tiles render here because no CMS images are attached; in the CMS the Color template disables the grayscale filter.
No template (grayscale, centered)
Trusted by teams everywhere
Full color, small, names, left-aligned
Trusted by teams everywhere
Shared settings library1 file
// Shared display template settings — spread these into displayTemplate() settings
// objects so every block exposes the same controls with the same plain-English labels.
//
// sortOrder is reserved 0-5 for the shared settings below. Block-specific inline
// settings must use 10+ so the two never collide in the CMS editor panel.
type SelectSetting = {
editor: "select";
displayName: string;
sortOrder: number;
choices: Record<string, { displayName: string; sortOrder: number }>;
};
// Background color options — for any block that renders with a card or section background
export const BACKGROUND: { background: SelectSetting } = {
background: {
editor: "select",
displayName: "Background color",
sortOrder: 0,
choices: {
white: { displayName: "White", sortOrder: 0 },
offWhite: { displayName: "Off-white", sortOrder: 1 },
blue: { displayName: "Green", sortOrder: 2 },
blueGrad: { displayName: "Bright green", sortOrder: 3 },
purple: { displayName: "Teal", sortOrder: 4 },
dark: { displayName: "Dark", sortOrder: 5 },
transparent: { displayName: "None", sortOrder: 6 },
opal: { displayName: "Expressive (Opal)", sortOrder: 7 },
},
},
};
// Same choices as BACKGROUND, reordered so "None" (transparent) sits at sortOrder 0
// and becomes the display-template default the CMS applies when the editor hasn't
// picked a background. For flat/content blocks that should inherit the parent's
// background rather than paint a white card. Card blocks keep BACKGROUND (White default).
export const BACKGROUND_NONE_DEFAULT: { background: SelectSetting } = {
background: {
editor: "select",
displayName: "Background color",
sortOrder: 0,
choices: {
transparent: { displayName: "None", sortOrder: 0 },
white: { displayName: "White", sortOrder: 1 },
offWhite: { displayName: "Off-white", sortOrder: 2 },
blue: { displayName: "Green", sortOrder: 3 },
blueGrad: { displayName: "Bright green", sortOrder: 4 },
purple: { displayName: "Teal", sortOrder: 5 },
dark: { displayName: "Dark", sortOrder: 6 },
opal: { displayName: "Expressive (Opal)", sortOrder: 7 },
},
},
};
// Same choices as BACKGROUND, reordered so "Bright green" (blueGrad, the green
// gradient) sits at sortOrder 0 and becomes the display-template default the CMS
// applies when the editor hasn't picked a background. For hero blocks whose code
// intends a brand-green gradient (a plain White default would flatten them).
export const BACKGROUND_BRAND_DEFAULT: { background: SelectSetting } = {
background: {
editor: "select",
displayName: "Background color",
sortOrder: 0,
choices: {
blueGrad: { displayName: "Bright green", sortOrder: 0 },
white: { displayName: "White", sortOrder: 1 },
offWhite: { displayName: "Off-white", sortOrder: 2 },
blue: { displayName: "Green", sortOrder: 3 },
purple: { displayName: "Teal", sortOrder: 4 },
dark: { displayName: "Dark", sortOrder: 5 },
transparent: { displayName: "None", sortOrder: 6 },
opal: { displayName: "Expressive (Opal)", sortOrder: 7 },
},
},
};
// Same choices as BACKGROUND, reordered so "Off-white" sits at sortOrder 0 and
// becomes the display-template default the CMS applies when the editor hasn't
// picked a background. For blocks whose code intends an off-white surface.
export const BACKGROUND_OFFWHITE_DEFAULT: { background: SelectSetting } = {
background: {
editor: "select",
displayName: "Background color",
sortOrder: 0,
choices: {
offWhite: { displayName: "Off-white", sortOrder: 0 },
white: { displayName: "White", sortOrder: 1 },
blue: { displayName: "Green", sortOrder: 2 },
blueGrad: { displayName: "Bright green", sortOrder: 3 },
purple: { displayName: "Teal", sortOrder: 4 },
dark: { displayName: "Dark", sortOrder: 5 },
transparent: { displayName: "None", sortOrder: 6 },
opal: { displayName: "Expressive (Opal)", sortOrder: 7 },
},
},
};
// Text color — for any block with visible text. "auto" keeps the historic
// behaviour of deriving text color from the chosen background, so unset content
// renders exactly as it did before this setting existed.
export const TEXT_COLOR: { textColor: SelectSetting } = {
textColor: {
editor: "select",
displayName: "Text color",
sortOrder: 1,
choices: {
auto: { displayName: "Automatic", sortOrder: 0 },
dark: { displayName: "Dark", sortOrder: 1 },
muted: { displayName: "Muted", sortOrder: 2 },
brand: { displayName: "Green", sortOrder: 3 },
teal: { displayName: "Teal", sortOrder: 4 },
light: { displayName: "Light", sortOrder: 5 },
},
},
};
// Heading size — for any block with a visible heading or title
export const HEADING_SIZE: { headingSize: SelectSetting } = {
headingSize: {
editor: "select",
displayName: "Heading size",
sortOrder: 2,
choices: {
xl: { displayName: "Extra large (H1)", sortOrder: 0 },
lg: { displayName: "Large (H2)", sortOrder: 1 },
md: { displayName: "Medium (H3)", sortOrder: 2 },
sm: { displayName: "Small (H4)", sortOrder: 3 },
},
},
};
// Heading size with a Medium (H3) default — for compact blocks (e.g. product
// cards) where an Extra large (H1) default is too big and overflows. Same choices
// as HEADING_SIZE, reordered so "Medium (H3)" sits at sortOrder 0 and becomes the
// display-template default the CMS applies when the editor hasn't picked a size.
export const HEADING_SIZE_CARD: { headingSize: SelectSetting } = {
headingSize: {
editor: "select",
displayName: "Heading size",
sortOrder: 2,
choices: {
md: { displayName: "Medium (H3)", sortOrder: 0 },
lg: { displayName: "Large (H2)", sortOrder: 1 },
sm: { displayName: "Small (H4)", sortOrder: 2 },
xl: { displayName: "Extra large (H1)", sortOrder: 3 },
},
},
};
// Text alignment — for blocks where centering or right-alignment is meaningful
export const TEXT_ALIGN: { textAlign: SelectSetting } = {
textAlign: {
editor: "select",
displayName: "Text alignment",
sortOrder: 3,
choices: {
left: { displayName: "Left", sortOrder: 0 },
center: { displayName: "Center", sortOrder: 1 },
right: { displayName: "Right", sortOrder: 2 },
},
},
};
// Font style — VC Nudge (display) and Die Grotesk (body) are both installed.
// The `modern`/`classic` keys are stored in published content, so they stay as-is.
export const FONT_STYLE: { fontStyle: SelectSetting } = {
fontStyle: {
editor: "select",
displayName: "Font family",
sortOrder: 4,
choices: {
modern: { displayName: "Display (VC Nudge)", sortOrder: 0 },
classic: { displayName: "Body (Die Grotesk)", sortOrder: 1 },
mono: { displayName: "Captions (Roboto Mono)", sortOrder: 2 },
},
},
};
// Body text size — for blocks where the prose reading size should be adjustable
export const TEXT_SIZE: { textSize: SelectSetting } = {
textSize: {
editor: "select",
displayName: "Text size",
sortOrder: 5,
choices: {
sm: { displayName: "Small", sortOrder: 0 },
md: { displayName: "Medium", sortOrder: 1 },
lg: { displayName: "Large", sortOrder: 2 },
},
},
};
// ─── Tailwind class lookups ────────────────────────────────────────────────────
// Background → wrapper/text Tailwind classes
export const BG_CLASSES: Record<string, { wrapper: string; text: string; textMuted: string }> = {
white: { wrapper: "bg-surface-lowest border border-outline-variant", text: "text-on-surface", textMuted: "text-on-surface-variant" },
offWhite: { wrapper: "bg-surface-low border border-outline-variant", text: "text-on-surface", textMuted: "text-on-surface-variant" },
blue: { wrapper: "bg-brand/10 border border-brand/20", text: "text-brand", textMuted: "text-on-surface-variant" },
blueGrad: { wrapper: "bg-gradient-brand", text: "text-on-brand", textMuted: "text-on-brand-subtle" },
purple: { wrapper: "bg-tertiary/10 border border-tertiary/20", text: "text-tertiary", textMuted: "text-on-surface-variant" },
dark: { wrapper: "bg-on-surface", text: "text-surface-lowest", textMuted: "text-surface-high" },
transparent: { wrapper: "", text: "text-on-surface", textMuted: "text-on-surface-variant" },
opal: { wrapper: "opal-surface", text: "text-on-brand", textMuted: "text-on-brand-subtle" },
};
// Heading size → Tailwind text size classes
export const HEADING_CLASSES: Record<string, string> = {
xl: "text-5xl md:text-6xl",
lg: "text-4xl md:text-5xl",
md: "text-3xl md:text-4xl",
sm: "text-2xl md:text-3xl",
};
// Font style → Tailwind font-family classes
export const FONT_CLASSES: Record<string, string> = {
modern: "font-display",
classic: "font-body",
mono: "font-mono",
};
// Text color → Tailwind text color classes. "auto" is intentionally absent:
// resolveStyleClasses() treats a missing entry as "follow the background".
export const TEXT_COLOR_CLASSES: Record<string, { text: string; textMuted: string }> = {
dark: { text: "text-on-surface", textMuted: "text-on-surface-variant" },
muted: { text: "text-on-surface-variant", textMuted: "text-on-surface-variant" },
brand: { text: "text-brand", textMuted: "text-brand/70" },
teal: { text: "text-tertiary", textMuted: "text-tertiary/70" },
light: { text: "text-surface-lowest", textMuted: "text-surface-high" },
};
// Text size → Tailwind prose size classes
export const TEXT_SIZE_CLASSES: Record<string, string> = {
sm: "text-sm",
md: "text-base",
lg: "text-lg",
};
// Text alignment → Tailwind text-align classes
export const TEXT_ALIGN_CLASSES: Record<string, string> = {
left: "text-left",
center: "text-center",
right: "text-right",
};
// ─── Resolver ──────────────────────────────────────────────────────────────────
export type DisplaySettings = Record<string, string | boolean> | undefined;
export type StyleFallbacks = {
background?: string;
textColor?: string;
headingSize?: string;
fontStyle?: string;
textAlign?: string;
textSize?: string;
};
export type ResolvedStyles = {
wrapper: string;
text: string;
textMuted: string;
heading: string;
font: string;
align: string;
size: string;
invert: boolean;
};
// Backgrounds whose resolved foreground is light - rich text on these needs
// .richtext-invert so headings / list markers stay legible.
const LIGHT_FG_BACKGROUNDS = new Set(["dark", "blueGrad", "opal"]);
const DEFAULTS: Required<StyleFallbacks> = {
background: "transparent",
textColor: "auto",
headingSize: "lg",
fontStyle: "modern",
textAlign: "left",
textSize: "md",
};
// Single entry point for every block's display-template styling. Blocks pass their
// own historic defaults via `fallbacks` so adding a setting never changes how
// already-published content renders.
export function resolveStyleClasses(ds: DisplaySettings, fallbacks: StyleFallbacks = {}): ResolvedStyles {
const pick = (key: keyof StyleFallbacks) =>
(ds?.[key] as string) || fallbacks[key] || DEFAULTS[key];
const bg = BG_CLASSES[pick("background")] ?? BG_CLASSES.transparent;
// "auto" (and any unknown value) means: take text color from the background.
const override = TEXT_COLOR_CLASSES[pick("textColor")];
return {
wrapper: bg.wrapper,
text: override?.text ?? bg.text,
textMuted: override?.textMuted ?? bg.textMuted,
heading: HEADING_CLASSES[pick("headingSize")] ?? HEADING_CLASSES.lg,
font: FONT_CLASSES[pick("fontStyle")] ?? FONT_CLASSES.modern,
align: TEXT_ALIGN_CLASSES[pick("textAlign")] ?? TEXT_ALIGN_CLASSES.left,
size: TEXT_SIZE_CLASSES[pick("textSize")] ?? TEXT_SIZE_CLASSES.md,
invert:
pick("textColor") === "light" ||
(pick("textColor") === "auto" && LIGHT_FG_BACKGROUNDS.has(pick("background"))),
};
}
// Convenience: the three controls every editor-facing block should expose.
export const STANDARD_STYLE_SETTINGS = {
...BACKGROUND,
...TEXT_COLOR,
...FONT_STYLE,
};
Heroes#
Full-width blocks with their own template settings: the Hero Default template controls alignment, height, heading size, and font; the Product Hero Compact template reduces height.
Hero, Default template, centered
Product Hero, Compact template