klehm / content-blocks-kit
Ready-to-use block types for ContentBlocks (Text, Title, Image, Tabs).
Package info
github.com/Klehm/content-blocks-kit
Type:symfony-bundle
pkg:composer/klehm/content-blocks-kit
Requires
- php: >=8.2
- klehm/content-blocks: ^1.0
- symfony/form: ^6.4 || ^7.0 || ^8.0
- symfony/framework-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/security-csrf: ^6.4 || ^7.0 || ^8.0
- symfony/translation: ^6.4 || ^7.0 || ^8.0
- symfony/validator: ^6.4 || ^7.0 || ^8.0
- twig/twig: ^3.10
Requires (Dev)
- phpunit/phpunit: ^11.0
- symfony/asset: ^6.4 || ^7.0 || ^8.0
This package is auto-updated.
Last update: 2026-08-14 12:59:10 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. choicesvalues 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.defaultsonly 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.4for overrides to take priority. Earlier versions manually registered the vendortemplates/path under@ContentBlocksKit, which shadowed the host'stemplates/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