This release is a pre-release and may not be stable for production use.
derico.blicca.promoblock
A Promo block for the Aurora editor in Plone Blicca. Blicca is the former Plone Classic UI. The block renders a promo card with a kicker, a title, a description, an optional image and up to two calls to action, or makes the whole card one link. Everything on the card is written by the author in the sidebar. Nothing is pulled from another content item.
Typical uses:
- a call to action at the end of a page ("Get in touch", "Book a demo");
- a highlighted announcement with a picture beside the text;
- a card linking to a campaign page, with a kicker above the headline.
The block needs plone.blicca.auroraeditor,
which brings the Aurora editor to Blicca. Its editor half is also a plain
Aurora block package, @derico/aurora-promo-block, that can be used in an
Aurora frontend directly, see Using the block in Aurora.
Features
- Authored content. Kicker, title, description, image, two actions and a card link, all written in the sidebar. Nothing is required, so a half-finished promo can be saved and published.
- Image left, right or above. With an image, the card is two columns with the image on the chosen side. Without one, or below a width of 34rem, the card stacks and centres.
- Two actions, each a button or a link. Each action has a label, a link
and a style. Links can be site content picked from the browser, or a typed
URL,
mailto:ortel:address. - Card link. If no action has a label, a single link makes the whole card clickable.
- Responsive images. Only the reference to the image is stored. The
public page serves the picture variant that fits the block width and the
placement, with
srcsetandsizes, computed on every load. An AVIF alternative is included when the host'splone.namedfilesupports it. A rescaled or deleted picture never leaves a broken<img>behind. - Honest editor preview. The canvas shows which fields are still empty and explains anything the author typed that will not render, such as an action with a label but no link.
- One markup, one stylesheet for the public page, the editor canvas and an Aurora frontend.
- Themeable through nineteen
--promo-*CSS custom properties. - Block width and background colour come from the editor's regular block styling controls.
Requirements
- Plone 6.0 or later
plone.blicca.auroraeditor1.0.0a4 or later
The JavaScript bundle is committed to the package. No Node is needed at install time.
Installation
Add the package to your project's dependencies:
# pyproject.toml
dependencies = [
"derico.blicca.promoblock",
]
Then install Derico Blicca Promoblock from Plone's Add-ons control
panel, or apply the derico.blicca.promoblock:default profile. Uninstalling
removes the block registration again.
Using the block
Insert the Promo block in the Aurora editor and fill in the sidebar. The canvas is a live preview; all editing happens in the sidebar.
Default fieldset
| Field | What it does |
|---|---|
| Kicker | Short line above the title |
| Title | The headline, rendered as an h2 |
| Description | Plain text below the title |
| Image | Pick or upload a picture. The editor's image widget is used, so it looks the same as in every other block |
| Image placement | Left, right or center. Shown only once an image is set. Center puts the picture above the text |
Actions fieldset
| Field | What it does |
|---|---|
| Primary action label, link, style | The first call to action. Style is Button or Link |
| Secondary action label, link, style | The second one, same shape |
| Card link | Makes the whole card clickable. Shown only while both action labels are empty |
Styling fieldset: block width, and a background colour if the site offers a palette.
Rules worth knowing:
- An action renders only with both a label and a link. A label without a link, or a link without a label, renders nothing and the canvas says so.
- As soon as either action has a label, the card link is ignored. Its value stays stored and comes back when the labels are cleared. There is never a link nested inside another link.
- The link fields accept a picked content item or typed text. Typed links
must be a path or use
http,https,mailtoortel. Anything else is dropped. - An image whose target has been deleted stops rendering on the next load. The card reflows to the no-image layout and the canvas shows a notice.
- The image is decorative and rendered with an empty
alt. The title and description carry the meaning.
Rendered markup
Every element appears only when it has content, and no container is emitted
empty, so an empty promo is one <div>.
<a class="promo-cardlink" href="…"> <!-- only with an active card link -->
<div class="promo has--align--left">
<picture class="promo-image"> <!-- only with an image -->
<source type="image/avif" srcset="…" sizes="…"> <!-- public page, when AVIF is available -->
<img src="…" srcset="…" sizes="…" width="…" height="…"
alt="" loading="lazy" decoding="async">
</picture>
<div class="promo-copy">
<p class="promo-kicker">…</p>
<h2 class="promo-title">…</h2>
<p class="promo-description">…</p>
<div class="promo-actions"> <!-- only with a rendering action -->
<a class="promo-cta promo-cta-button" href="…">…</a>
<a class="promo-cta promo-cta-link" href="…">…</a>
</div>
</div>
</div>
</a>
- The root
div.promoalways carrieshas--align--<value>with the placement that is actually in effect:centerwhenever there is no image, whatever was stored. - The public page carries the responsive attributes on the
<img>and, when the host supplies AVIF, one AVIF<source>in front of it. The React renderer emits the same elements and classes with a plain<img>. - The action style is spelled as a class,
promo-cta-buttonorpromo-cta-link. There is no class for primary or secondary; DOM order tells them apart. - Block width and background classes are added by the host on a wrapper around this markup, as for every Aurora block.
How it works
The block stores its text fields, the image reference, the link strings, the placement and the action styles. The image is the only field the server touches:
- On every load, a
plone.restapiblock serialization transformer resolves the stored image reference and injectsimage_url,image_scalesandimage_fieldinto the block data, plusimage_ref, the reference those three were derived from. A reference whose target is gone getsimage_refbut noimage_url, which is how both renderers know to draw the no-image layout. - On save, a matching deserialization transformer strips all four keys again, so derived image data is never written to the database.
- The public renderer picks the smallest of the site's picture variants
(
small,medium,largeorfullwidth) that covers the width the image can get, derived from the block width and the placement, and puts itssrcseton the<img>with a matchingsizes. When supplied byplone.namedfile, the AVIF twin of that ladder goes into a<source>in front. An SVG or an external image URL gets a plain<img>. - The React renderer reads
image_url. A freshly picked image that has not been serialized yet is previewed from the reference itself, andimage_reflets the canvas tell a stale derived set from a fresh one.
The transformers are registered for content with the IBlocks behavior and
for the site root, so a promo in a footer stored on the site root works on
every page. Nested blocks inside a text container are transformed too.
Two renderers emit the same markup: a Chameleon template registered as the
@@aurora-block-promo view for the public page, and a React view
component for the editor canvas and for Aurora frontends. The rules that
decide what renders (defaults, the click rule, the link screen, the
effective placement) are spelled once per language, in promo_data.py and
in bundle-src/src/promo/data.ts, and the Python test suite reads the
TypeScript file to keep the two in step. Both renderers read the shared
fixture tests/anatomy-cases.json in their test suites, so they cannot
drift apart unnoticed.
The two-column layouts collapse to the stacked layout through a container query on the block's own width, not a media query. The editor canvas and the published page are different widths at the same viewport, and a viewport query would get the canvas wrong.
Theming
Nineteen CSS custom properties are the whole styling interface. Set them on
:root or on your theme's own scope root; they inherit into the block. Do
not set them on .promo itself and do not override the block's rules
directly: the stylesheet is @scope-wrapped, and a scoped declaration wins
over an unscoped one of equal specificity, so a plain rule in the theme
would lose.
The block declares none of these properties. Every default is spelled at its
point of use as var(--promo-x, <default>), so a value set on :root
inherits in and wins without specificity games. The defaults are plain
literals rather than --plone-* or --aurora-* tokens, because those
vocabularies differ between themes and are absent in an Aurora frontend. A
theme that wants its own scale sets the property to its own token, for
example --promo-title-size: var(--plone-text-2xl).
| property | default | what it controls |
|---|---|---|
--promo-gap |
2rem |
gap between the image column and the copy column |
--promo-flow |
0.75rem |
rhythm inside the copy stack, including between the two actions |
--promo-padding |
0 |
inner padding of the block root |
--promo-measure |
60ch |
max width of the copy |
--promo-border |
none |
border on the block root |
--promo-radius |
0 |
corner radius of the block root |
--promo-image-width |
1fr |
the image's grid track at left / right |
--promo-image-ratio |
auto |
aspect-ratio of the image box |
--promo-image-radius |
0 |
corner radius of the image |
--promo-kicker-size |
0.875rem |
kicker font size |
--promo-kicker-color |
currentColor |
kicker colour |
--promo-title-size |
1.75rem |
title font size |
--promo-description-size |
1rem |
description font size |
--promo-link-color |
currentColor |
the link action style, and the card link |
--promo-cta-bg |
CanvasText |
button background |
--promo-cta-fg |
Canvas |
button text colour |
--promo-cta-hover-bg |
color-mix(in oklab, var(--promo-cta-bg, CanvasText) 88%, var(--promo-cta-fg, Canvas)) |
button background on hover |
--promo-cta-radius |
0.375rem |
button corner radius |
--promo-cta-border |
none |
button border |
Example, a rounded card with a brand-coloured button:
:root {
--promo-padding: 2rem;
--promo-border: 1px solid var(--my-border-color);
--promo-radius: 1rem;
--promo-image-radius: 0.5rem;
--promo-image-ratio: 4 / 3;
--promo-cta-bg: var(--my-brand-color);
--promo-cta-fg: white;
}
Notes:
- The padding defaults to
0because the host's background band already pads the block. Turn padding on together with a border or a background of your own. - The copy width is always capped, so a centred promo at full block width does not run its text across the whole bleed.
- The image ratio defaults to
auto. Setting a ratio crops the image withobject-fit: cover. - The image width is a grid track, so
1fr,22remand40%all work. - The hover background is derived from whatever fill and text colour the theme set, so a theme that sets only the fill still gets a hover state.
- A button border exists so a theme can express a ghost button.
- There is no background or foreground property. The block's ground is the host's background band, chosen with the block's own background control.
- The block sets no focus outline and no font weight, line height or text
transform. The host's focus style and the theme's heading styles apply
unchanged. One visible consequence: the title has the theme's
h2weight on the public page and the editor's body weight in the canvas.
On the dark background band, Blicca forces every descendant's text colour
to the band's foreground. That flattens the kicker colour, the link colour
and the button text colour on that band. The button background still
applies, so set --promo-cta-bg to a colour that works with the band's text
colour.
Adding a property is a minor release. Removing or renaming a property, or changing a default, is a breaking change. See ADR 0002 for the reasoning.
Using the block in Aurora
The editor half lives in bundle-src/ as the npm package
@derico/aurora-promo-block (not yet published). It registers the block and
its three widgets through the usual install(config) entry point and resolves
every other field to an upstream Aurora widget.
- The Python package must still be installed on the backend. The React
viewreads the derived image keys the server injects. Without it the image does not render. - The description is a single line. It asks for the
textareawidget, which the Blicca editor registers and Aurora does not. - The image field has no label and shows no current selection. This is the host image widget's own shape in both hosts. The canvas is where the chosen picture is visible.
- Bring your own styling. The stylesheet is scoped to the Blicca roots and does not apply in an Aurora frontend.
- The block has been verified against the registry the Aurora installers build, not inside a running Aurora application.
Development
The JavaScript build output is committed into
src/derico/blicca/promoblock/static/. Rebuild and commit it whenever
bundle-src/src/ changes; CI fails when the committed bundle does not match
the source.
cd bundle-src
pnpm install
pnpm build # writes ../src/derico/blicca/promoblock/static/promo-block.{js,css}
pnpm test # reads the built bundle, so build first
pnpm typecheck
uv run --extra test pytest
test/seam-lockstep.test.ts checks that the property table in this README
matches the stylesheet literally, so keep the two in step. Design decisions
are recorded in docs/adr/.
Every change to a GenericSetup profile XML file needs an upgrade step, even
in an alpha release. Scaffold it with plonecli add upgrade_step.
License
GPL-2.0-or-later
Author
Maik Derstappen, derico, md@derico.de
Metadata
Release files for derico.blicca.promoblock 1.0.0a4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| derico_blicca_promoblock-1.0.0a4.tar.gz | 770.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| derico_blicca_promoblock-1.0.0a4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 887.4 kB
Release files / derico_blicca_promoblock-1.0.0a4.tar.gz
| Download URL | derico_blicca_promoblock-1.0.0a4.tar.gz |
|---|---|
| Size | 770.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
04707ee1932963c26af0799fd6786dbbcf5e2308dc235d79bab6e61ab9a65d04
|
|
BLAKE2b-256 checksum How to use checksums |
a775929ca554637ecb30da07b7b07c84b0953dbf6bf5b1a9c4f988a71a1d179f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / derico_blicca_promoblock-1.0.0a4-py3-none-any.whl
| Download URL | derico_blicca_promoblock-1.0.0a4-py3-none-any.whl |
|---|---|
| Size | 117.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
76facd32758929f01adfbca1548f79bb1b196b29268c26dffa0e899dc8dc1eb7
|
|
BLAKE2b-256 checksum How to use checksums |
dc36cd737171fa0354b04897388a8b216587fe367b6a66991ce40a6367d6e26e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|