Claude-skill-registry cms-component-builder
Build Optimizely CMS components for Astro v5. Use when creating/modifying components, pages, experiences, or working with GraphQL fragments, opti-type.json, or opti-style.json files (project)
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cms-component-builder" ~/.claude/skills/majiayu000-claude-skill-registry-cms-component-builder && rm -rf "$T"
skills/data/cms-component-builder/SKILL.mdCMS Component Builder
Build production-ready Optimizely CMS components with proper GraphQL integration, daisyUI styling, and TypeScript type safety.
When to Use This Skill
ALWAYS use this skill when the user asks to:
- Create a new component, page, or experience (e.g., "create a testimonial component", "add a new page type")
- Modify or update
files (content type definitions).opti-type.json - Modify or update
files (style/display template definitions).opti-style.json - Create or modify GraphQL fragments (
or.graphql
files).dam.graphql - Add components to
or similar aggregation filesallComponents.graphql - Debug component rendering issues
- Fix GraphQL type errors related to components
- Push/sync content types or styles to the CMS
- Understand how the component system works
Critical Instructions
YOU MUST FOLLOW THESE STEPS IN ORDER:
-
READ THE GUIDES FIRST - This skill includes comprehensive guides that YOU MUST reference:
- For creatingCONTENTTYPE-GUIDE.md
files.opti-type.json
- For creatingSTYLE-GUIDE.md
files.opti-style.json
- For creatingGRAPHQL-PATTERNS.md
and.graphql
files.dam.graphql
-
EXAMINE EXISTING COMPONENTS - Always look at similar existing components as examples before creating new ones. Check
for patterns.src/cms/components/ -
CREATE ALL REQUIRED FILES - A complete component needs 5 files minimum:
- Component template with TypeScript.astro
- Content type definition.opti-type.json
- Style definition(s) (can have multiple).opti-style.json
- Base GraphQL fragment.graphql
- DAM-enabled GraphQL fragment.dam.graphql
-
INTEGRATE PROPERLY - After creating files:
- Add fragment to
src/cms/components/allComponents.graphql - Push to CMS:
andyarn type:push ComponentNameyarn style:push StyleName - Wait 10 seconds for Optimizely Graph sync
- Generate types:
yarn codegen
- Add fragment to
Quick Start Example
Creating a new "Testimonial" component:
# 1. Create component directory mkdir -p src/cms/components/TestimonialComponent # 2. Create required files src/cms/components/TestimonialComponent/ ├── Testimonial.astro # Component template ├── Testimonial.opti-type.json # Content type definition ├── DefaultTestimonial.opti-style.json # Default style ├── testimonial.graphql # Base GraphQL fragment └── testimonial.dam.graphql # DAM GraphQL fragment
Required Files Explained
Every component needs at least 5 files:
1. .astro
- Component Template
.astroIMPORTANT: Always follow the pattern from existing components like
Button.astro
--- import type { TestimonialFragment, DisplaySettingsFragment, } from '../../../../__generated/sdk'; import type { ContentPayload } from '../../../graphql/shared/ContentPayload'; import { isEditContext } from '../../shared/utils.ts'; const isCmsEdit = isEditContext(Astro.url); export interface Props { key: string; data: TestimonialFragment; displaySettings: DisplaySettingsFragment[]; displayTemplateKey: string; contentPayload: ContentPayload; } const { key, data, displaySettings, displayTemplateKey, contentPayload } = Astro.props as Props; // Your styling logic here - reference STYLE-GUIDE.md const componentClass = 'component-testimonial'; --- <div data-epi-block-id={isCmsEdit && key || undefined} class={componentClass}> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <p class="text-lg italic" set:html={data.Quote?.html}></p> <div class="card-actions justify-end"> <p class="font-bold">{data.Author}</p> </div> </div> </div> </div>
Key Points:
- Import types from
(NOT from__generated/sdk
)@/graphql/__generated/graphql - Include ALL required props:
,key
,data
,displaySettings
,displayTemplateKeycontentPayload - Use
for CMS editing contextdata-epi-block-id - Use
for rich text fieldsset:html - Add a component class for CSS targeting
2. .opti-type.json
- Content Type
.opti-type.jsonSee
CONTENTTYPE-GUIDE.md for complete guide on creating content types.
3. .opti-style.json
- Style Definitions
.opti-style.jsonSee
STYLE-GUIDE.md for complete guide on creating style definitions.
4. .graphql
+ .dam.graphql
- GraphQL Fragments
.graphql.dam.graphqlALWAYS create both versions. See
GRAPHQL-PATTERNS.md for complete details.
Base (
):testimonial.graphql
fragment Testimonial on Testimonial { Quote { html } Author AuthorTitle AuthorImage { ...ContentUrl } }
DAM (
):testimonial.dam.graphql
fragment Testimonial on Testimonial { Quote { html } Author AuthorTitle AuthorImage { ...ContentUrl ...ContentReferenceItem } }
Component Integration
1. Add to allComponents.graphql
fragment AllComponentsExceptGrid on _IComponent { ...Text ...Button ...Testimonial # Add your component here }
Important: Add to
AllComponentsExceptGrid, NOT AllComponents
2. Sync to CMS & Generate Types
# Push content type and styles to CMS yarn type:push ComponentName yarn style:push StyleName # ⚠️ Wait ~10 seconds for Optimizely Graph to sync # Then generate TypeScript types yarn codegen
3. Verify Integration
Check
__generated/graphql.ts for your component type.
Component Locations
- Components:
- Reusable UI (Button, Card, Hero)src/cms/components/ - Pages:
- Page types (ArticlePage, LandingPage)src/cms/pages/ - Experiences:
- Experience templatessrc/cms/experiences/ - Compositions:
- Layout elements (Row, Column)src/cms/compositions/
GraphQL Shared Fragments
Quick reference (full details in
GRAPHQL-PATTERNS.md):
- Links with url, title, target, textLinkUrl
- Content reference URLsContentUrl
- Simple link arraysLinkCollection
- DAM metadata (ContentReferenceItem
only).dam.graphql
- Display/styling settingsDisplaySettings
- Page metadataPageUrl
Rich text fields: Always use
{ html }:
Body { html } Description { html }
Styling with daisyUI + TailwindCSS
Prefer daisyUI components:
<button class="btn btn-primary btn-lg"> <div class="card card-compact"> <div class="hero min-h-screen">
Use theme variables:
<div class="bg-base-100 text-base-content">
Mobile-first responsive:
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
Complete Workflow (FOLLOW IN ORDER)
Step 1: Research & Planning
- Ask the user for requirements: What should this component do? What properties does it need?
- Find similar components: Use
to find similar components inGlobsrc/cms/components/ - Read example files: Study at least one similar component to understand the patterns
Step 2: Create Component Files
Create the component directory and all required files:
mkdir -p src/cms/components/ComponentNameComponent cd src/cms/components/ComponentNameComponent
Create these files in order:
-
- Content type definitionComponentName.opti-type.json- Reference
for structureCONTENTTYPE-GUIDE.md - Look at similar component's
as example.opti-type.json - Set
for componentsbaseType: "component" - Set
for components usable in visual editorcompositionBehaviors: ["elementEnabled"]
- Reference
-
- Default style templateDefaultComponentName.opti-style.json- Reference
for structureSTYLE-GUIDE.md - Set
to match your component's keycontentType - Set
for the default styleisDefault: true - Create meaningful display settings with choices
- Reference
-
- Base GraphQL fragmentcomponentName.graphql- Reference
GRAPHQL-PATTERNS.md - Fragment name must match component name (PascalCase)
- Include all properties from
.opti-type.json - Use shared fragments:
,...LinkUrl
, etc....ContentUrl
- Reference
-
- DAM-enabled GraphQL fragmentcomponentName.dam.graphql- Copy from
componentName.graphql - Add
to content references...ContentReferenceItem - This enables DAM metadata for images/videos
- Copy from
-
- Component templateComponentName.astro- Follow the pattern from existing components (see example above)
- Import types from
__generated/sdk - Include all required props
- Handle display settings for styling
- Use daisyUI classes for styling
Step 3: Integrate with System
-
Add to allComponents.graphql
# src/cms/components/allComponents.graphql fragment AllComponentsExceptGrid on _IComponent { ...ExistingComponent ...YourNewComponent # Add this line } -
Push to CMS
# Push content type yarn type:push ComponentName # Push style template yarn style:push DefaultComponentName # ⚠️ CRITICAL: Wait 10-15 seconds for Optimizely Graph to sync -
Generate TypeScript Types
# After waiting for Graph sync yarn codegen
Step 4: Verify & Test
-
Check generated types in
:__generated/sdk.ts- Look for
typeComponentNameFragment - Verify all properties are present
- Look for
-
Check for TypeScript errors:
yarn tsc --noEmit -
Test in dev server:
yarn dev -
Verify in CMS:
- Component appears in CMS UI
- All properties are editable
- Style options work correctly
Verification Checklist
YOU MUST CHECK ALL OF THESE before completing:
-
✓ All 5 required files created:
ComponentName.astroComponentName.opti-type.jsonDefaultComponentName.opti-style.jsoncomponentName.graphqlcomponentName.dam.graphql
-
✓ Content type (
) is valid:.opti-type.json- Has
,key
,displayNamebaseType - All properties defined correctly
- Uses correct property types
- Has
-
✓ Style template (
) is valid:.opti-style.json- Has
,key
,displayNamecontentType
matches component keycontentType- Has
for default styleisDefault: true - All settings have choices
- Has
-
✓ Both GraphQL fragments created:
for base version.graphql
with.dam.graphql
for DAM...ContentReferenceItem
-
✓ Fragment added to
:allComponents.graphql- Added to
(NOTAllComponentsExceptGrid
)AllComponents
- Added to
-
✓ Pushed to CMS successfully:
completedyarn type:push ComponentName
completedyarn style:push StyleName- Waited 10-15 seconds for Graph sync
-
✓ Types generated successfully:
completed without errorsyarn codegen
type exists inComponentNameFragment__generated/sdk.ts
-
✓ Component template (
) follows patterns:.astro- Imports from
__generated/sdk - Has all required props
- Uses
for CMS editingdata-epi-block-id - Uses daisyUI classes for styling
- Handles display settings
- Imports from
-
✓ No TypeScript errors:
passesyarn tsc --noEmit
-
✓ Component works in dev server:
runs without errorsyarn dev
Resources
All guides are bundled with this skill for quick reference:
-
GraphQL Patterns: See
for:GRAPHQL-PATTERNS.md- Complete fragment syntax and structure
- DAM vs non-DAM differences
- Real-world examples from existing components
- Shared fragment catalog
- Integration patterns
-
Content Type Creation: See
for:CONTENTTYPE-GUIDE.md- Content type structure and syntax
- Property definitions and types
- Display settings configuration
- Real-world examples
-
Style Creation: See
for:STYLE-GUIDE.md- Style definition structure
- Display settings mapping to CSS classes
- daisyUI and TailwindCSS patterns
- Responsive variants
Common Patterns
Component with links:
Links { ...LinkCollection }
Component with images:
# Base Image { ...ContentUrl } # DAM Image { ...ContentUrl ...ContentReferenceItem }
Component with metadata:
_metadata { types displayName }
Common Issues & Troubleshooting
Issue: GraphQL fragment not found after codegen
Solution:
- Check that fragment is added to
allComponents.graphql - Ensure you pushed to CMS:
yarn type:push ComponentName - Wait 10-15 seconds for Optimizely Graph to sync
- Run
againyarn codegen
Issue: TypeScript errors in .astro file
Solution:
- Verify imports are from
not__generated/sdk@/graphql/__generated/graphql - Check that all Props interface fields are present
- Run
to regenerate typesyarn codegen - Check that fragment name matches component name exactly (case-sensitive)
Issue: Component doesn't appear in CMS
Solution:
- Verify
completed successfullyyarn type:push ComponentName - Check that
is set correctly inbaseType.opti-type.json - Check that
is setcompositionBehaviors: ["elementEnabled"] - Clear CMS cache and refresh
Issue: Display settings not working
Solution:
- Verify
completed successfullyyarn style:push StyleName - Check that
incontentType
matches component.opti-style.jsonkey - Ensure
is set for default styleisDefault: true - Verify style mapping exists in component's styling helper file
Issue: Rich text fields not rendering HTML
Solution:
- Use
instead ofset:html={data.FieldName?.html}{data.FieldName.html} - Ensure GraphQL fragment requests
for rich text fields{ html }
Issue: Content references (images/videos) not loading
Solution:
- Check GraphQL fragment includes
...ContentUrl - For DAM assets, verify
includes.dam.graphql...ContentReferenceItem - Check that
orurl?.default
is used in templateurl?.hierarchical
Pages and Experiences
This skill also handles Pages and Experiences, which follow the same patterns:
Pages (
src/cms/pages/):
- Set
instead ofbaseType: "Page""Component" - Include SEO settings and page admin settings
- Add to appropriate page aggregation file
- May include
for content areasmayContainTypes
Experiences (
src/cms/experiences/):
- Define experience templates
- Include composition structure
- Reference Row, Column, and Section compositions
Using Svelte 5 for Interactive Components
This project uses Svelte 5 (latest version with runes) for client-side interactivity. Svelte components are primarily used for admin/utility interfaces, NOT for CMS components.
When to Use Svelte
Use Svelte for:
- Admin interfaces (like
pages)/opti-admin - Interactive forms and utilities
- Complex client-side state management
- Real-time features (SSE, WebSockets)
DO NOT use Svelte for:
- CMS components (use
instead).astro - Content rendering from Optimizely
- SEO-critical content
Svelte 5 Key Patterns
State Management with Runes:
<script lang="ts"> // Props using $props() rune interface Props { title: string; count?: number; } let { title, count = 0 }: Props = $props(); // Reactive state using $state() rune let currentCount = $state(0); let isLoading = $state(false); // Derived state using $derived rune let doubleCount = $derived(currentCount * 2); // Side effects using $effect rune $effect(() => { console.log('Count changed:', currentCount); }); // Functions function increment() { currentCount++; } </script> <div> <h1>{title}</h1> <p>Count: {currentCount}</p> <p>Double: {doubleCount}</p> <button onclick={increment}>Increment</button> </div>
Important Svelte 5 Changes:
- Use
instead of$props()export let - Use
instead of$state()
for reactive variableslet - Use
instead of$derived
for computed values$: - Use
instead of$effect()
for side effects$: - Use
instead ofonclick={}on:click={} - Use
for two-way bindingbind:value={}
Example: Admin Component with TypeScript
See
src/pages/opti-admin/components/_CmsSync.svelte for a complete example showing:
- TypeScript interfaces with Props
- State management with
and$state()$derived - Event handling with EventSource (SSE)
- Lifecycle with
onMount() - Conditional rendering with
{#if} - List rendering with
{#each}
Embedding Svelte in Astro
--- // MyPage.astro import MyCoolComponent from './_components/MyCoolComponent.svelte'; const someData = { title: 'Hello', count: 5 }; --- <div> <MyCoolComponent client:load title={someData.title} count={someData.count} /> </div>
Client Directives:
- Load immediately on page loadclient:load
- Load when browser is idleclient:idle
- Load when component is visibleclient:visible
- Only run on client, no SSRclient:only="svelte"
Remember: Every component must be production-ready with:
- Complete integration into the system
- Proper TypeScript types
- Consistent daisyUI styling
- Both GraphQL fragment versions (.graphql and .dam.graphql)
- Comprehensive verification before completion