klehm/content-blocks-kit

Ready-to-use block types for ContentBlocks (Text, Title, Image, Tabs).

Maintainers

Package info

github.com/Klehm/content-blocks-kit

Homepage

Issues

Type:symfony-bundle

pkg:composer/klehm/content-blocks-kit

Transparency log

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.0-RC2 2026-08-14 09:45 UTC

README

Ready-to-use block types for klehm/content-blocks.

The kit is self-contained: no Tailwind/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.

Included blocks

Type What it is
title Heading with visual size, semantic tag, and palette text color
text Plain paragraph text with palette text color
rich_text WYSIWYG rich text, on TinyMCE or CKEditor (options.editor)
image Image with size preset / custom size, fit, align, link, caption, rounded corners
gallery Image grid or slider (arrows) with columns, fit, rounded corners
button Call-to-action button (variants, sizes, alignment)
card Image/title/text/button tiles as a grid or list
list Bulleted / checkmark / numbered list
icon A single icon from the shipped icon set
alert Info / success / warning / error callout
divider Horizontal rule (style + color)
accordion Collapsible panels (native <details>, zero JS)
table Columns + rows data table
embed Responsive YouTube / Vimeo embed
breadcrumb Breadcrumb trail
html_raw Raw HTML escape hatch (disabled by default — opt in)
tabs Tabbed panels

Installation

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.

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):

<link rel="stylesheet" href="{{ path('content_blocks_kit_asset_css') }}">

Retheme by overriding the --cb-kit-* custom properties (or the classes) in your own stylesheet loaded after it.

Stimulus controllers

Enable the kit's controllers in your host assets/controllers.json under the @klehm/content-blocks-kit package: cb-tinymce / cb-ckeditor (rich text — whichever editor you selected) and cb-gallery (gallery slider).

Configuring blocks

Each block exposes four levers under content_blocks_kit.blocks.<type>:

Key Purpose
enabled false un-registers the block's service — it never reaches the picker.
options Block-level knobs (e.g. max_columns), merged over the block's coded ones.
choices Per-field allow-list restricting/reordering a ChoiceType field.
defaults Per-field overrides of a block's initial data (what a new block starts with).
# config/packages/content_blocks_kit.yaml
content_blocks_kit:
    blocks:
        tabs: { enabled: false }                # drop a block entirely
        html_raw: { enabled: true }             # opt into a default-disabled block
        gallery:
            options: { max_columns: 4 }         # cap the column choices
        button:
            choices:
                variant: [primary, secondary]   # only these two, in this order, in the picker
                size: [md, lg]
            defaults:
                variant: secondary              # new buttons start as "secondary"
                align: center
        title:
            defaults: { size: h1 }              # new titles default to h1 size

Notes:

  • Blocks omitted from config are enabled with their coded defaults — except html_raw, which is disabled by default: it renders unescaped markup ({{ html|raw }}), so it trusts its editors and must be opted in explicitly.
  • choices values not offered by the block are ignored; an empty or all-invalid list falls back to the full set (the select is never empty). Restricting the picker does not invalidate content already stored with a now-hidden value — validation still accepts the block's full coded set.
  • defaults only apply to fields the block declares; unknown keys are ignored.

Colors

All color fields — icon and divider colors, the title and text blocks' text color, and the rich-text (TinyMCE) swatches — draw from the one core palette declared in content_blocks.palette (see the main package README). Add a named color there once and it appears everywhere:

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

Discovering the surface

List every block with its options, choice fields (default marked *) and data defaults — read straight from the code, so it never goes stale:

bin/console content-blocks-kit:blocks          # all blocks
bin/console content-blocks-kit:blocks button   # one block

Overriding block templates

Drop a file at the matching relative path under templates/bundles/ContentBlocksKitBundle/ to override any template shipped by this kit — e.g. templates/bundles/ContentBlocksKitBundle/block/image/view.html.twig overrides the image view.

Requires klehm/content-blocks-kit >= 0.1.0-alpha.4 for overrides to take priority. Earlier versions manually registered the vendor templates/ path under @ContentBlocksKit, which shadowed the host's templates/bundles/ContentBlocksKitBundle/ directory.

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, not a loophole: the 17 block classes are deliberately non-final and their protected methods are public API under the package's semver guarantee.

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

# config/packages/content_blocks_kit.yaml
content_blocks_kit:
    blocks:
        button: { enabled: false }   # de-registers the kit service entirely
use ContentBlocks\BlockType\AsContentBlock;
use ContentBlocks\Kit\Block\ButtonBlock;

#[AsContentBlock]
final class AppButtonBlock extends 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' => ''];
    }
}

You inherit the kit's view template and its stored data shape; only the form grows. Override the template too if the new field has to render.

The extension points AbstractKitBlock guarantees:

Method Purpose
choiceFields() single source for the block's choice maps — choices() and choiceConstraint() both read it, so adding an option here reaches the form and the validator
defaults() initial values for the block's data
describe() introspection, consumed by content-blocks-kit:blocks

Note getDefaultData() is final: it merges defaults() with the host's content_blocks_kit.blocks.<type>.defaults config, and letting a subclass replace it would silently drop that config. Add your keys in defaults() instead.

Keep getType() inherited. Two services claiming one type id is a silent conflict — BlockTypeRegistry keeps whichever was registered last — which is why the kit's own service has to be disabled rather than merely shadowed.

File uploads

ImageBlock uses the main package's upload brick (ImageUploadType, the /_content-blocks/upload endpoint and FileStorageInterface — all in klehm/content-blocks now). Enable it via the bundle config:

# config/packages/content_blocks.yaml
content_blocks:
    upload:
        directory: '%kernel.project_dir%/public/uploads/content-blocks'
        public_prefix: '/uploads/content-blocks'

A file can be picked from the dialog or dropped anywhere on the field — both go through the same endpoint and the same limits.

Image optimization

The kit stays dependency-free, so it serves an uploaded file as stored and only controls its display box (width/height, object-fit, loading="lazy"). Every image it renders — image, gallery items, card media — goes through the core's ImageUrlResolverInterface, whose default returns the source untouched.

Alias that interface in your app (to a CDN URL builder, a LiipImagine bridge…) and the three views emit srcset/sizes with no template override. The image block hands the resolver the display width it computed (sm=400, md=800, lg=1200, or the custom width) — exactly what a resizing resolver needs.

Worked example, compression and WebP included: Compress and convert images. Seam reference: Host services.

Documentation & contributing

Full documentation and development setup live in the monorepo: github.com/klehm/content-blocks-project

Backward compatibility. From 1.0.0, what is covered by semver — and what is deliberately not — is listed in the backward compatibility page. Anything tagged @internal sits outside the promise.

License

MIT