From optimizely-content-modelling
Produces a structured content model table for Optimizely blocks, pages, or components from a design screenshot, wireframe, or bullet-point brief. Use this skill whenever the user asks for a "content model", "property list", "block model", "block properties", "page type schema", or wants to translate a design into Optimizely fields — including when they drop in a screenshot, Figma export, or rough list of elements and expect field types, required flags, and editor guidance back. Also trigger when the user mentions Optimizely field types (XhtmlString, ContentArea, ContentReference, SelectOne, etc.), asks to "model this block", "define the properties for", or wants editor helper text written for CMS properties. The skill covers both Optimizely CMS PaaS (CMS 12, .NET attributes) and SaaS / Visual Builder, and flags where the two differ.
How this skill is triggered — by the user, by Claude, or both
Slash command
/optimizely-content-modelling:optimizely-content-modellingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Turn a design (screenshot, wireframe, or bullet-point brief) into a proposed Optimizely content model, expressed as a single table that an editor, developer, and analyst can all read without translation.
Turn a design (screenshot, wireframe, or bullet-point brief) into a proposed Optimizely content model, expressed as a single table that an editor, developer, and analyst can all read without translation.
When someone asks for a content model, they want the bridge between a visual design and the properties a developer will actually create. The table is that bridge. It needs to be:
Output nothing except the table (and, when warranted, a short note above or below it explaining a PaaS/SaaS divergence or a judgement call). No preamble, no section headers, no "here's your content model" throat-clearing.
Always use exactly this table structure:
| Property name | Field type | Required | Translatable | Rules and limits | Helper text shown to editors |
|---------------|------------|----------|--------------|------------------|------------------------------|
| Heading | Plain text | Yes | Yes | Max 80 characters | Shown as the block's main heading. |
Column rules:
ctaLabel or CtaLabel). One property per row. No developer-style camelCase unless the user has explicitly asked for the internal name.Yes / No. Default to No unless the field is genuinely load-bearing (a heading on a hero, the image on an image block, the target URL on a CTA). Over-requiring is a common editor pain point.Yes / No. Default to Yes for any human-readable text. Default to No for structural choices (toggles, variant selectors, image references, content references, dates, colours). See the translatable defaults section below.The table below lists field types in platform-agnostic language (what to use by default), with the PaaS and SaaS equivalents in brackets. Use the agnostic name unless the user has said which flavour they're targeting.
String / SaaS: String) — single-line text. Use for headings, labels, short captions. Set a character limit when the text affects layout (see the character-limit guidance below for when to omit).LongString / SaaS: LongString) — multi-line plain text. Use for short paragraphs, SEO descriptions, image alt text that might run long.XhtmlString / SaaS: XhtmlString or RichText) — formatted text with inline links, bold, lists. Use for body copy. Always specify which toolbar/formatting is allowed in rules (e.g. "Bold, italic, links only — no headings").Url / SaaS: Url) — a URL, internal or external. Prefer ContentReference for internal links where possible — it survives page moves.[SelectOne] attribute / SaaS: SelectOne) — one option from a fixed list. Use for variant pickers, layout switches, theme choices. List the options in rules.[SelectMany] / SaaS: SelectMany) — several options from a fixed list. Use sparingly — if editors need this often, consider a tag or category field instead.Boolean / SaaS: Boolean) — yes/no switch. Name it so the "on" state reads naturally ("Show author byline", not "Author byline visibility").Number or FloatNumber / SaaS: Integer or Float) — a numeric value. Always give a min/max in rules — "Max number of items: between 1 and 12".ContentReference / SaaS: ContentReference) — a link to a single piece of content. Specify the allowed types in rules ("Articles only", "Any page").ContentArea / SaaS: ContentArea) — a list of content items, usually blocks. Specify allowed types and a max item count. This is the workhorse of Optimizely composition — use it for featured content, card lists, anything with "up to N items".ContentReference with UIHint = "image" / SaaS: ContentReference with image allowed types) — a reference to an image, video, or document in Media. Specify allowed media types and aspect ratio guidance.DateTime / SaaS: DateTime) — a date, with or without time. Say which in rules.UIHint / SaaS: String with selection factory) — a colour value. Prefer a fixed palette (single select of tokens) over a free colour picker — it keeps brand consistency.Style / PaaS: approximate with SelectOne) — presentation variants attached to a component. In SaaS these are first-class; in PaaS they're conventionally modelled as a "Display variant" single select. If the design shows multiple visual treatments of the same block, flag this as a Styles candidate in SaaS or a variant select in PaaS.Getting this column right is the difference between a localisation team that thanks you and one that raises three tickets a week. Default reasoning:
When in doubt, state the assumption in the helper text so the editor knows.
Unless the user gives you limits, default to these and mention them in the helper text so editors know:
Character limits matter for text that affects layout on the page — headings, straplines, card summaries, CTA buttons. For structural or label text — field labels on a form, section headings on a profile tab, small UI strings whose length the design constrains visually — omit the limit unless the design actually has a specific breakpoint at a specific count. Writing a number where the design doesn't require one invites a refinement debate ("why 40 and not 50?") that doesn't matter. State the default value in rules instead, and trust the design system.
Before adding a property, ask: would the value be identical on every instance of this component across the site? If yes, it's a design-system constant (a hard-coded string, a design token, a component prop), not a CMS property. Modelling constants as properties creates maintenance surface for no editor benefit — editors get a field they'll never change, and any change has to be made in N places instead of one.
Common false positives:
When in doubt, ask: has the design or the brief shown this varying across pages? If not, don't model it.
Editors hate aggressive required flags, especially on blocks used across many pages where a field only applies sometimes. Default posture:
If a field's presence depends on another field's value (e.g. "Description" only matters when "Show description" is on), mark it Not required and explain the dependency in helper text.
Identify each distinct editable element — text, image, CTA, list of items, variant/layout choice. For each, ask:
A repeated card pattern is almost always a ContentArea of a smaller block, not N sets of fields on the parent. Call this out — it's the single most common modelling improvement.
Take the user's names as a starting point but fix them to editor-friendly sentence case. If the brief says "heading text", the property name is "Heading". If the brief includes obvious technical shorthand ("CTA", "RTE", "CA for featured items"), expand it in the property name and preserve the intent.
If the brief lists toggles like "truncate headings [toggle]" — those are Boolean fields, Not required, Not translatable, with helper text explaining what the toggle controls on the rendered page.
Add a one-line note above the table when any of these apply:
Keep notes short — one line each. Don't lecture.
Input: screenshot of a hero with a heading, short strapline, a CTA button, and a background image.
Output:
| Property name | Field type | Required | Translatable | Rules and limits | Helper text shown to editors |
|---|---|---|---|---|---|
| Heading | Plain text | Yes | Yes | Max 80 characters | The main hero heading. Keep it punchy and action-oriented. |
| Strapline | Plain text | No | Yes | Max 120 characters | Supporting line below the heading. Leave blank to hide. |
| CTA label | Plain text | No | Yes | Max 25 characters | Button text. Use a verb — "Get started", "Book a demo". |
| CTA link | Content reference | No | No | Any page or external URL | Where the button goes. Leave blank to hide the button. |
| Background image | Media reference | Yes | No | Image only. Landscape, min 1920×800px. | Full-width background. Avoid busy imagery behind the text area. |
Input: bullet list covering a "Featured articles carousel", "Most popular leaderboard", "Topic carousel" — all with similar fields but some variant-specific.
Output:
Modelled as a single block with a variant selector. Where a field only applies to some variants, the helper text says so.
| Property name | Field type | Required | Translatable | Rules and limits | Helper text shown to editors |
|---|---|---|---|---|---|
| Display variant | Single select | Yes | No | Options: Latest news grid, Featured carousel, Most popular leaderboard, Topic carousel, Specialism segment | Controls the block's layout and how articles are sourced. |
| Heading | Plain text | No | Yes | Max 80 characters | Heading shown above the article list. Leave blank to hide. |
| Description | Rich text | No | Yes | Bold, italic, links only. Max 300 characters. | Intro copy below the heading. Not shown on "Latest news grid". |
| Featured articles | Content area | No | No | Article pages only. Max 8 items. | Used by Featured, Most popular, and Specialism variants when the source is manual. |
| Category | Content reference | No | No | Category taxonomy only | Used by Topic carousel and Specialism variant when sourcing articles by category. |
| Max items to load | Number | No | No | Between 3 and 12. Defaults to 6. | How many articles the block shows. Ignored by Most popular leaderboard. |
| Show "View all" button | Toggle | No | No | — | Adds a link to the category or listing page below the articles. |
| Show author images | Toggle | No | No | — | Displays author avatars on each article card. |
| Truncate headings | Toggle | No | No | — | Cuts long article headings to one line on the card. |
| Truncate descriptions | Toggle | No | No | — | Cuts long article summaries to two lines on the card. |
Don't do this:
| Property | Type | Req | Trans | Notes | Help |
|---|
Stick to the full column headings. They map to real CMS concepts and shorthand loses that.
Don't do this either:
| Heading | String | true | true | none | Enter the heading. |
"String" is internal. "Enter the heading" is worthless. "None" in rules is often a missed opportunity — set a character limit when the text affects layout (headings, straplines, card summaries, CTAs), an item count for lists, and an allowed-types list for references. For structural/label text where the design doesn't require a specific count, it's fine to leave the limit off and state the default value instead.
One model per response. If the input clearly contains multiple distinct blocks or a page composed of several blocks, either:
Don't invent extra blocks beyond what the input shows. If the input is a hero, model the hero — not the hero, the card grid below it, and the footer.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub tomrobinson26/qa-skills --plugin optimizely-content-modelling