TypeScript to Component.yml
When to Use
Use this when you have a TypeScript interface or Props type definition and need to produce a complete SDC
component.ymlschema. This is a step-by-step workflow for the most common translation task.
Decision: Type Mapping
| TypeScript Type | JSON Schema Type | Notes |
|---|---|---|
string |
type: string |
|
number |
type: number |
Use integer if always whole numbers |
boolean |
type: boolean |
|
'a' \| 'b' \| 'c' |
type: string + enum: [a, b, c] |
Add meta:enum for labels |
2 \| 3 \| 4 \| 5 \| 6 |
type: integer + enum: [2, 3, 4, 5, 6] |
Numeric union |
string \| undefined (optional ?) |
Omit from required list |
|
Record<string, string> |
type: object |
Rarely used in SDCs |
string[] |
type: array + items: { type: string } |
Prefer slots for complex arrays |
React.ReactNode |
Move to slots: |
Renderable content is always a slot |
() => void / functions |
SKIP | Use behaviors for interactivity |
React.HTMLAttributes<T> |
SKIP | Handled by Drupal's attributes variable |
Pattern: Complete Workflow
Step 1: Extract the TypeScript interface
interface CardProps {
title: string;
description?: string;
variant?: 'default' | 'compact' | 'side';
size?: 'sm' | 'md' | 'lg' | 'xl';
bordered?: boolean;
url?: string;
onClick?: () => void;
className?: string;
children: React.ReactNode;
actions?: React.ReactNode;
}
Step 2: Classify each property
| Property | Category | SDC Mapping |
|---|---|---|
title |
String data | props -- string type |
description |
Optional string data | props -- string type (not required) |
variant |
String union (styling) | variants section |
size |
String union (sizing) | props with enum + meta:enum |
bordered |
Boolean flag | props -- boolean type |
url |
URL string | props -- use $ref: "ui-patterns://url" |
onClick |
Callback function | SKIP -- no Twig equivalent |
className |
CSS classes | SKIP -- handled by attributes |
children |
Renderable content | slots -- default content slot |
actions |
Renderable content | slots -- named actions slot |
Step 3: Write the component.yml
name: Card
description: "Cards display content in a contained, flexible format."
group: "Data display"
variants:
default: { title: Default }
compact: { title: Compact }
side: { title: Side }
slots:
title:
title: Title
description:
title: Description
content:
title: Content
actions:
title: Actions
props:
type: object
properties:
size:
title: Size
type: string
enum: [sm, md, lg, xl]
meta:enum:
sm: Small
md: Medium
lg: Large
xl: Extra large
bordered:
title: Bordered
type: boolean
url:
title: URL
$ref: "ui-patterns://url"
Step 4: Write the Twig template
{% set classes = [
'card',
variant and variant == 'compact' ? 'card-compact',
variant and variant == 'side' ? 'card-side',
size ? 'card-' ~ size,
bordered ? 'card-bordered',
] %}
{% set tag_name = 'div' %}
{% if url %}
{% set tag_name = 'a' %}
{% set attributes = attributes.setAttribute('href', url) %}
{% endif %}
<{{ tag_name }} {{ attributes.addClass(classes) }}>
{% if title %}
<h2 class="card-title">{{ title }}</h2>
{% endif %}
{% if description %}
<p>{{ description }}</p>
{% endif %}
{{ content }}
{% if actions %}
<div class="card-actions">{{ actions }}</div>
{% endif %}
</{{ tag_name }}>
Workflow Checklist
- Extract -- Copy the full interface/Props type
- Classify -- Mark each property as: prop, slot, variant, skip, or preprocess
- Skip these --
React.Ref, callbacks (() => void),className,style,React.HTMLAttributes - Convert to slots -- Any
React.ReactNodeorJSX.Elementtype becomes a named slot - Convert variants -- String unions that control CSS modifier classes go in
variants: - Convert enums -- Other string/number unions become
enumwithmeta:enumlabels - Set required -- Only props that the component cannot function without
- Add defaults -- Use
|default()in Twig for any prop that has a default value in React - Handle URL props -- Use
$ref: "ui-patterns://url"for URL-type props - Handle ID props -- Use
$ref: "ui-patterns://identifier"for ID-type props
Common Mistakes
- Wrong: Trying to represent
React.ReactNodeastype: string→ Right: Renderable content must be a slot to preserve HTML and cache metadata - Wrong: Putting
classNameas a prop → Right: Drupal'sattributesvariable already handles class merging - Wrong: Making every prop
required→ Right: Only truly required props (those that break the component if missing) - Wrong: Forgetting
meta:enumlabels → Right: Site builders see raw values without them