Criando um novo componente do Palette
A practical guide to adding a Single Directory Component (SDC) to Palette: what every component must contain, and how the Style (variant) setting works. See CLAUDE.md for the architecture overview, styles.md for extending a component from another module/theme.
1. A anatomia de um componente
Every component lives in its own folder under components/:
components/<id>/
<id>.component.yml # obrigatório — metadados + schema de props/slots
<id>.twig # obrigatório — a marcação
<id>.css # opcional — estilos co-localizados
<id>.js # opcional — comportamento co-localizado<id>is the machine name (lowercase, underscores). Palette exposes the component ascanvas_palette:<id>.- Core SDC auto-discovers the folder and auto-generates a library
core/components.canvas_palette--<id>from the co-located.css/.js. You do not register the library yourself.
1a. <id>.component.yml — as chaves obrigatórias
$schema: https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json
name: My Thing # rótulo legível mostrado no editor
group: Palette # categoria na lista de componentes — sempre "Palette"
status: experimental # experimental | stable | deprecated
description: 'One line describing what it does.'
props:
type: object
properties:
# … see below …
slots:
# … optional, see 1c …1b. Props
Cada prop é uma propriedade de JSON-schema. Formas comuns usadas neste kit:
props:
type: object
properties:
title:
type: string
title: Title
examples: ['Default title'] # valor placeholder/semente do editor
text:
type: string
title: Text
contentMediaType: text/html # rich text …
x-formatting-context: block # … edited with the block CKEditor
examples: ['<p>Some copy.</p>']
count:
type: integer
title: Count
default: 3
image:
$ref: json-schema-definitions://canvas.module/image
type: object
title: ImageRules that matter (all enforced by Palette — get them wrong and the component silently disables itself and vanishes from the editor):
- A
required:prop MUST have anexamplesvalue. If you don't want a default, make the prop optional (leave it out ofrequired:) rather than giving it an empty example. examples[0]is the value seeded onto a newly placed instance — treat it as the editor default/placeholder.default:is the runtime fallback the Twig sees when the prop is unset.- Enum props list a
meta:enummap for human labels (see §2). - Optional integer props error when cleared in the editor; prefer a
default: 0that the Twig treats as "unset" instead of making it clearable.
1c. Slots (componentes contêiner)
Container components (Carousel, Tiles, Stats…) hold child components in static slots, and usually pair with a dedicated *_item component:
slots:
items:
title: Items
description: 'Coloque um My Item por linha.'
expected: ['canvas_palette:my_item']
minItems: 1Slots are rendered in Twig with {% block <slot> %}{% endblock %}. Palette blocks runtime-dynamic slots, so the number of slots is fixed at definition time.
1d. <id>.twig
Twig SDC simples. Props e slots são variáveis/blocos de nível superior:
<div class="cp-my-thing cp-my-thing--{{ style|default('default') }}">
{% if title %}<h2 class="cp-my-thing__title">{{ title }}</h2>{% endif %}
{% if text %}<div class="cp-my-thing__body">{{ text }}</div>{% endif %}
{% block items %}{% endblock %}
</div>Conventions: BEM class names cp-<id>, cp-<id>__element, cp-<id>--modifier.
1e. JS that uses Drupal.behaviors / once / a third-party library
The auto-generated component library gets core/drupal injected only when the component declares libraryOverrides. So any component whose .js uses Drupal.behaviors, once(), or a shared library must declare its dependencies:
libraryOverrides:
dependencies:
- core/drupal
- core/once
- canvas_palette/glightbox # bibliotecas de terceiros são anexadas da mesma formaWithout this, Drupal/once are undefined at runtime. (See tabs, accordion, carousel for real examples.)
1f. Elementos apenas do editor
To show something only inside the Palette editor preview (grab padding, empty outlines…), gate it on the canvas_is_preview flag Palette injects into the Twig context and emit a cp-<id>--preview modifier — it never reaches published pages:
{% set classes = ['cp-my-thing', canvas_is_preview ? 'cp-my-thing--preview' : ''] %}The JS-side equivalent is window.frameElement?.dataset?.canvasPreview === 'true'.
2. How the Style setting works
The "Style" setting is Palette's single convention for component variants. There is no per-style Twig or JS branching — it is one enum prop turned into a CSS class.
2a. The style prop
Most components declare a single enum string prop named style (labelled "Style", or sometimes "Layout" / "Columns"):
style:
type: string
title: Style
default: default
enum:
- default
- colorful
meta:enum:
default: Default
colorful: Colorful
examples: [default]enum— the machine values.meta:enum— the human labels shown in the select.default/examples— the starting value.
2b. O Twig interpola isso em um modificador BEM
The Twig writes the value verbatim onto the root element as a modifier class:
<div class="cp-my-thing cp-my-thing--{{ style|default('default') }}">So style: colorful renders class="cp-my-thing cp-my-thing--colorful".
2c. O CSS usa o modificador como chave
Each style is just a CSS rule set keyed on .cp-<id>--<value>:
.cp-my-thing--colorful {
background: linear-gradient(135deg, #0973f2, #7333e5);
color: #fff;
}That is the entire mechanism: enum value → cp-<id>--<value> class → CSS.
2d. Ela flutua para o topo do formulário do editor
canvas_palette_move_style_prop_first() gives any prop literally named style a very low #weight, so the Style selector always appears first in the component-inputs sidebar. This runs from canvas_palette_form_component_instance_form_alter() for every sdc.canvas_palette.* component — you get it for free just by naming the prop style. (Components with a custom inputs-form handler that re-weights props must still leave style at the top.)
2e. Adicionando um novo valor de Style a um componente existente
Adding an enum value is an additive, safe change:
- Add the value to
enum:and a label tometa:enum:. - Add the
.cp-<id>--<value>CSS. - Rebuild (see §3). The new value appears on newly placed instances and after a hard editor reload; existing instances keep their pinned version.
Shared style conventions in this kit (see CLAUDE.md for the exact token values): a colorful value and an optional eyebrow kicker prop, styled to the landing-page design system. Reuse the hard-coded token values there rather than inventing new colours.
3. Build e pegadinhas de versionamento
After adding or editing a component, rebuild from the site root:
php vendor/drush/drush/drush.php crOn this OSPanel stack a CLI drush cr often does not reach the browser — flush through the web (admin Configuration → Development → Performance → Clear all caches, or a throwaway web/flush.php). See CLAUDE.md → "Local environment quirk".
Canvas freezes each component's schema as a versioned config entity (sdc.canvas_palette.<id>). The consequences:
- Editing
*.component.ymldoes not live-update placed instances. New props / enum values show up on newly placed instances and after a hard editor reload. - Additive changes are safe (new enum value, new optional prop). Changing an existing prop's field type is not — it throws and disables the whole component. Revert the type to restore it.
If a component ends up disabled after a change, regenerate and re-enable:
\Drupal::service('Drupal\canvas\ComponentSource\ComponentSourceManager') ->generateComponents(); $c = \Drupal::entityTypeManager()->getStorage('component') ->loadUnchanged('sdc.canvas_palette.<id>'); if ($c && !$c->status()) { $c->enable()->save(); }Verify it stays enabled across a second generate — a fix that only survives one manual re-enable is not a real fix.
Checklist para um novo componente
components/<id>/<id>.component.yml with $schema, name, group: Palette, status, description.
Props defined; every required prop has examples.
<id>.twig with cp-<id> BEM classes.
A style enum prop (+ meta:enum) if the component has variants, wired to cp-<id>--{{ style }} and matching CSS.
libraryOverrides.dependencies if the JS uses Drupal.behaviors / once / a third-party library.
Slots + a paired *_item component if it's a container.
Rebuilt (through the web); component shows ENABLED across two generates.