Skip to content

Block Kit ​

klehm/content-blocks-kit is an optional package of 19 ready-to-use block types for ContentBlocks. Install it and you get a full block palette out of the box — no configuration required.

The kit is self-contained: no Tailwind, no Bootstrap, no LiipImagine, no icon library. Every block renders neutral cb-kit-* markup styled by a single shipped stylesheet, so it drops into any host regardless of its CSS setup.

The 19 blocks ​

BlockWhat it is
titleHeading with a visual size decoupled from its semantic tag
textPlain paragraph text with a palette color
rich_textWYSIWYG rich text, on TinyMCE or CKEditor
imageImage with size, fit, align, link, caption, rounded corners
galleryImage grid or arrow slider
buttonCall-to-action button (variants, sizes, alignment)
button_groupOne to three buttons in a row, sharing size and alignment
cardImage/title/text/button tiles as a grid or list
listBulleted / checkmark / numbered list
iconA single icon from the shipped icon set
alertInfo / success / warning / error callout
dividerHorizontal rule (style + color)
accordionCollapsible panels (native <details>, zero JS)
tableColumns + rows data table
embedResponsive YouTube / Vimeo embed
videoSelf-hosted video file in a native <video> player
breadcrumbBreadcrumb trail
tabsTabbed panels
html_rawRaw-HTML escape hatch (disabled by default)

Each block has its own reference page with its full configurable surface — see the sidebar.

Installation ​

bash
composer require klehm/content-blocks klehm/content-blocks-kit

The blocks are auto-registered via Symfony autoconfiguration — no config needed to get all of them (except html_raw, which is disabled by default).

Front stylesheet (required) ​

Kit blocks render with neutral cb-kit-* classes styled by a stylesheet the kit serves at a public route. Include it once in your front layout (it also flows into the builder preview):

twig
<link rel="stylesheet" href="{{ cb_kit_stylesheet_url() }}">

The URL carries a hash of the file, so browsers and CDNs keep it for a year and fetch the new one after an upgrade. A plain path('content_blocks_kit_asset_css') works too, revalidated every five minutes.

Retheme by overriding the --cb-kit-* custom properties (set them on :root), or the classes in your own stylesheet.

Stimulus controllers ​

Two blocks need a Stimulus controller. Enable them in your host assets/controllers.json under the @klehm/content-blocks-kit package — rich_text has one per selectable editor, so enable the one you configured (or both, if you are unsure):

ControllerNeeded by
cb-tinymcerich_text on its default editor
cb-ckeditorrich_text when options.editor: ckeditor
cb-gallerygallery (slider layout only)

Colors ​

Every color field in the kit — icon and divider colors, the title and text blocks' text color, and the rich-text editor's swatches — draws from the one core palette declared in content_blocks.palette. Add a named color there once and it appears everywhere:

yaml
# config/packages/content_blocks.yaml
content_blocks:
    palette:
        - { label: 'Brand', color: '#eb0540' }

The kit's own tokens ​

The palette covers what an editor picks per block. What a developer sets once, for every kit block on the site, is a set of seven CSS custom properties declared by kit.css:

TokenDefaultDrives
--cb-kit-primary#4f46e5primary button fill, list markers, icon default, breadcrumb links
--cb-kit-primary-contrast#fffffftext on a primary fill
--cb-kit-secondary#64748bsecondary button fill
--cb-kit-secondary-contrast#fffffftext on a secondary fill
--cb-kit-border#d1d5dbdivider, card, table and accordion rules, gallery arrows
--cb-kit-text#1f2937alert text
--cb-kit-radius8pxcorner radius across the kit

They are declared once, on :where(:root), and :where() carries zero specificity. Set them on :root for the whole site, or on any container for part of it; either wins without !important, whatever order the stylesheets load in:

css
/* your front stylesheet */
:root {
    --cb-kit-primary: #eb0540;
    --cb-kit-radius: 2px;
}

.dark-band {
    --cb-kit-border: #334155;
}

An alert's colours come from its tone (info, success, warning, error), not from these tokens: restyle .cb-kit-alert--<tone> for those.

These names are public surface, covered by the package's semver guarantee. Note they style content, on the published page — they have nothing to do with the --cb-* tokens that theme the builder chrome (styling guide).

One block adds tokens of its own, because its colors have no equivalent elsewhere in the kit — tabs, whose open tab has to sit on the panel it opens:

TokenDefaultDrives
--cb-kit-tabs-line#e5e7ebpanel border, and the rule under the tab row
--cb-kit-tabs-tab#6b7280a closed tab's label
--cb-kit-tabs-tab-active#1f2937the open tab's label (and any hovered one)
--cb-kit-tabs-panel-bg#ffffffpanel fill, reused by the open tab so it joins the panel
--cb-kit-tabs-accent#4f46e5keyboard focus ring

Same rule as above: declared on :where(:root), so :root or .cb-kit-tabs { --cb-kit-tabs-line: … } in your stylesheet wins.

Extending a kit block ​

Overriding a template changes what a block renders. When you need it to edit something the kit does not offer — one extra field, a different default, a narrower choice set — subclass the block instead. This is a supported path: the 19 block classes are deliberately non-final, and their protected methods are covered by the package's semver guarantee.

Disable the kit's service and register yours in its place, keeping the same type id so stored content keeps working:

yaml
# config/packages/content_blocks_kit.yaml
content_blocks_kit:
    blocks:
        button: { enabled: false }
php
#[AsContentBlock]
final class AppButtonBlock extends ContentBlocks\Kit\Block\ButtonBlock
{
    public function buildForm(FormBuilderInterface $builder, array $data): void
    {
        parent::buildForm($builder, $data);
        $builder->add('trackingId', TextType::class, ['required' => false, 'data' => $data['trackingId'] ?? '']);
    }

    protected function defaults(): array
    {
        return parent::defaults() + ['trackingId' => ''];
    }
}

Four things to know before you do:

  • getDefaultData() is final. It merges defaults() with the host's content_blocks_kit.blocks.<type>.defaults config, so replacing it would silently drop that config. Grow defaults() instead.

  • Keep getType() inherited, and disable the kit's service. Two services claiming one type id is a silent conflict — the registry keeps whichever was registered last, and which one that is depends on container order.

  • The config still applies. enabled: false only un-registers the kit's service; options, choices and defaults declared under that same type reach your subclass, merged over its own coded defaults. So subclassing to add one field does not cost you the configuration of everything else:

    yaml
    content_blocks_kit:
        blocks:
            divider:
                enabled: false                                     # yours takes over
                choices: { style: { solid: 'Solid', double: 'Double' } }
                defaults: { style: 'double' }

    A subclass that gives itself a new getType() is configured under that new id instead — the config is keyed by the type, never by the class.

    The type is read while the container is compiled, before any service exists, so a kit subclass's getType() must return a constant: it cannot depend on its constructor.

  • In tests, take the block from the registry. Since the config arrives through the constructor, new YourBlock() yields the coded surface — the block as its class defines it, with none of your YAML. That is the right object for a unit test of defaults() or choiceFields(), and the wrong one for asserting anything you configured: the assertion then describes the class, not the application, and passes or fails for a reason that has nothing to do with your config. Ask BlockTypeRegistry instead, and you get the instance the app actually renders with:

    php
    $block = self::getContainer()->get(BlockTypeRegistry::class)->get('divider');

The full contract, including choiceFields() and describe(), is in the package README.

Discovering the surface from the CLI ​

Every block's options, choice fields (default marked *) and data defaults are introspectable — read straight from the code, so the output never goes stale:

bash
bin/console content-blocks-kit:blocks              # all blocks, human-readable
bin/console content-blocks-kit:blocks button       # one block
bin/console content-blocks-kit:blocks --format=json   # machine-readable (drives these docs)

The per-block reference pages in this section are generated from that JSON, so they always match the installed version.

Next ​

Released under the MIT License.