+
+
Signal
+
+ Workshop notes, new arrivals, and downtime warnings. One message a month.
-
@@ -583,37 +764,44 @@ const NewsletterSignup = () => {
);
};
-export default NewsletterSignup;
+export default SignalSignup;
```
### Step 2: Add Styles
-Create `src/theme/_newsletterSignup.scss`:
+Create `src/theme/_signalSignup.scss`:
```scss
-.newsletter-signup {
+// VLT pads the footer's first child with `#footer > :first-child:not(:empty)`
+// (1,1,0). The sign-up band brings its own padding and its own background, so
+// that padding shows up as a gap in the footer's gradient above it.
+#footer > .pre-footer:has(.signal-signup) {
+ padding: 0;
+}
+
+.signal-signup {
background-color: var(--secondary-color);
color: var(--secondary-foreground-color);
padding: 4rem 2rem;
- .newsletter-container {
+ .signal-container {
max-width: var(--default-container-width);
margin: 0 auto;
text-align: center;
}
- .newsletter-title {
+ .signal-title {
font-size: 2rem;
margin-bottom: 1rem;
}
- .newsletter-description {
+ .signal-description {
font-size: 1.125rem;
margin-bottom: 2rem;
opacity: 0.9;
}
- .newsletter-form {
+ .signal-form {
display: flex;
gap: 1rem;
max-width: 500px;
@@ -621,7 +809,7 @@ Create `src/theme/_newsletterSignup.scss`:
flex-wrap: wrap;
justify-content: center;
- .newsletter-input {
+ .signal-input {
flex: 1;
min-width: 250px;
padding: 0.75rem 1rem;
@@ -636,7 +824,7 @@ Create `src/theme/_newsletterSignup.scss`:
}
}
- .newsletter-button {
+ .signal-button {
padding: 0.75rem 2rem;
background-color: var(--accent-color);
color: var(--accent-foreground-color);
@@ -661,12 +849,14 @@ Create `src/theme/_newsletterSignup.scss`:
Import in `src/theme/_main.scss`:
```scss
-@import './blocks/hero';
-@import './blocks/relatedItems';
+@import './blocks/button';
+@import './blocks/cover';
+@import './blocks/grid';
@import './blocks/listing';
-@import './productSummary';
-@import './site';
-@import './newsletterSignup';
+@import './blocks/slider';
+@import './blocks/teaser';
+@import './robotSummary';
+@import './signalSignup';
```
### Step 3: Register the Component to the Slot
@@ -674,22 +864,93 @@ Import in `src/theme/_main.scss`:
In `src/config/settings.ts`:
```typescript
-import NewsletterSignup from '../components/NewsletterSignup/NewsletterSignup';
+import SignalSignup from '../components/SignalSignup/SignalSignup';
export default function install(config: ConfigType) {
// ... previous config ...
config.registerSlotComponent({
- name: 'NewsletterSignup',
+ name: 'SignalSignup',
slot: 'preFooter',
- component: NewsletterSignup,
+ component: SignalSignup,
});
return config;
}
```
-The newsletter signup component will now appear before the footer on all pages, demonstrating how slots enable you to extend the layout without shadowing core components.
+The Signal sign-up now appears before the footer on every page, demonstrating how slots let you extend the layout without shadowing core components.
+
+## Swapping Structural Components
+
+Slots let you *add* to the layout. Sooner or later you will want to *replace* part of it—a navigation that your design calls for, a footer that VLT's does not cover.
+
+The old answer was to shadow the component by dropping a file into {file}`customizations`. VLT 8 offers a better one: its structural components are resolved through the registry at render time, so a project can substitute its own by registering it and naming it in configuration.
+
+### How It Works
+
+VLT registers each of its structural components as a utility under the name `vlt`, with a `type` naming the role:
+
+```typescript
+config.registerUtility({ name: 'vlt', type: 'navigation', method: Navigation });
+```
+
+A setting then decides which registered name renders for each role:
+
+```typescript
+config.settings.vlt = {
+ components: {
+ breadcrumbs: 'vlt',
+ footer: 'vlt',
+ header: 'vlt',
+ languageSelector: 'vlt',
+ logo: 'vlt',
+ mobileNavigation: 'vlt',
+ navigation: 'vlt',
+ searchWidget: 'vlt',
+ tags: 'vlt',
+ },
+};
+```
+
+These are the defaults, so out of the box nothing changes. The nine roles above are the ones you can swap.
+
+### Making the Swap
+
+Swapping a role takes two steps, and the Robotarium does not need either of them yet—the mechanism is worth knowing before you reach for it.
+
+First, register your implementation under a name of your own, against the role's `type`, in {file}`src/config/settings.ts`:
+
+```typescript
+config.registerUtility({
+ name: 'robotarium',
+ type: 'navigation',
+ method: MyNavigation,
+});
+```
+
+Both implementations now sit in the registry, and nothing renders differently yet. The second step selects yours:
+
+```typescript
+config.settings.vlt.components.navigation = 'robotarium';
+```
+
+That is the whole change. VLT's header resolves the navigation through the registry when it renders, so it picks up your component. Every other role keeps its `vlt` default, and VLT's own navigation stays registered under `vlt`—so reverting is a one-line edit, not a file deletion.
+
+The same two steps swap any of the nine roles.
+
+### Why Prefer This Over Shadowing
+
+- **Explicit.** The active component is named in configuration, not implied by a file's path.
+- **Composable.** Several implementations can coexist under different names, and you choose which one renders.
+- **Decoupled.** You bind to a role name, not to an internal module path that can move between VLT releases.
+- **Safe.** If a setting names a component that was never registered, VLT falls back to its own implementation rather than rendering nothing.
+- **Typed.** The keys of `config.settings.vlt.components` are a fixed set, so a misspelled role is a compile error rather than a silent no-op.
+
+```{note}
+Your add-on must be applied after `@kitconcept/volto-light-theme` so that `config.settings.vlt` exists when you assign to it.
+Keeping VLT last in your project add-on's `addons` list, as set up in the first chapter, is enough.
+```
## Site Customization Behaviors
@@ -702,6 +963,7 @@ Through the Plone UI, you can customize:
- **Complementary logo**: Second logo on the right side
- **Fat menu**: Enabled by default, can be disabled
- **Intranet header**: Alternative header for intranet sites
+- **Site flag**: The colored pill at the top left of the header
- **Actions**: Links at the top right
### Theme Customization Options
@@ -733,6 +995,8 @@ Block Model v3 introduces a unified container architecture that ensures consiste
- Improved spacing control through block categories
- Reduced maintenance overhead
+(bm3-two-container-label)=
+
### The Two-Container System
Every block in Block Model v3 follows this structure:
@@ -757,7 +1021,7 @@ Every block in Block Model v3 follows this structure:
```
**Main/Outer Container:**
-- Full width (extends edge to edge)
+- Spans the full layout width
- Handles background colors and theme variables
- Uses padding (not margin) for vertical spacing
- CSS Classes: `.block.${type}.category-${category}`
@@ -770,27 +1034,59 @@ Every block in Block Model v3 follows this structure:
### Enable Block Model v3
+VLT ships with `config.settings.blockModel = 2` and copies that value onto each block it has migrated. Opting in means setting the flag and re-applying it to those blocks, because they read the value at the time VLT was configured—which is before your add-on runs.
+
In your project's `src/config/settings.ts`:
```typescript
export default function install(config: ConfigType) {
- // Enable Block Model v3
+ // ... previous configuration ...
+
+ // Enable Block Model v3 globally
config.settings.blockModel = 3;
- config.blocks.blocksConfig.slate.blockModel = config.settings.blockModel;
- config.blocks.blocksConfig.title.blockModel = config.settings.blockModel;
- config.blocks.blocksConfig.__button.blockModel = config.settings.blockModel;
- // Define block categories for spacing
- config.blocks.blocksConfig.slate.category = 'inline';
- config.blocks.blocksConfig.title.category = 'title';
- config.blocks.blocksConfig.__button.category = 'action';
+
+ // Re-apply it to the blocks VLT has migrated
+ for (const type of ['slate', 'title', '__button']) {
+ config.blocks.blocksConfig[type].blockModel = config.settings.blockModel;
+ }
return config;
}
```
+```{important}
+Check each block's repository for the "BMv3 ready" banner before you flip it.
+```
+
### Block Categories
-Block categories determine spacing relationships between adjacent blocks. Categories are available at `config.blocks.blocksConfig.[$type].category`.
+Block categories determine spacing relationships between adjacent blocks, through the `category-${category}` class on the outer container. They are set at `config.blocks.blocksConfig.[$type].category`.
+
+VLT already assigns categories to the blocks it has migrated, so you do not need to repeat them:
+
+| Category | Blocks | Spacing behavior |
+| --- | --- | --- |
+| `inline` | `slate` | Flows as body text |
+| `title` | `title` | Opens a section |
+| `action` | `__button` | A call to action |
+| `cards` | `gridBlock` | Self-contained visual units |
+
+Your own blocks need a category too, and the useful instinct is to reach for one of these four before inventing a fifth. The Cover block renders a self-contained visual unit with its own background, which is what `cards` already describes, so it can reuse it.
+
+These two lines go in {file}`src/config/blocks.ts`, **after** the Cover registration from the previous chapter. They are worth adding even with the flag left at `2`—`blockModel` simply picks up whatever `config.settings.blockModel` currently holds:
+
+```typescript
+config.blocks.blocksConfig.cover.category = 'cards';
+config.blocks.blocksConfig.cover.blockModel = config.settings.blockModel;
+```
+
+```{warning}
+The file matters here, and so does the position within it.
+
+{file}`src/index.ts` calls `installSettings` before `installBlocks`, so at the time {file}`config/settings.ts` runs, `config.blocks.blocksConfig.cover` does not exist yet—putting these lines there alongside `config.settings.blockModel = 3` throws. They have to run after the block is registered, and after the flag is set, which {file}`config/blocks.ts` satisfies on both counts.
+```
+
+Add a category of your own only when a block genuinely spaces differently from all four. When you do, name it for the family it opens rather than for the block that prompted it, and remember that a category only means something if your stylesheets act on the `category-*` class it produces.
Vertical spacing between blocks is provided by the **upper block**:
- Block content should be flush with top of container
@@ -826,4 +1122,26 @@ Vertical spacing between blocks is provided by the **upper block**:
```
-Notice how the actual block content remains identical in both modes, while the framework containers handle all the differences in layout and editing functionality.
\ No newline at end of file
+Notice how the actual block content remains identical in both modes, while the framework containers handle all the differences in layout and editing functionality.
+### The `volto-bm3-compat` add-on
+
+Block Model v3 changes the markup a block renders into, so blocks written against the older model need their styles adapting. `@kitconcept/volto-bm3-compat` bridges that gap, and VLT declares it as an add-on of its own, so it is already in your dependency tree if you followed the install chapter.
+
+Check the block's repository for the "BMv3 ready" banner before enabling the new model on a block you did not write.
+
+## Checkpoint
+
+- `config.settings.vlt.components` lists the nine swappable roles, each still set to `vlt`. Naming a component that was never registered under one of them falls back to VLT's own rather than rendering nothing.
+- The Signal sign-up form appears above the footer on every page.
+- A Listing block set to the **Robot Fleet** variation shows each charge level as a labelled bar, the Book buttons line up along the bottom of the cards whatever the description lengths, and tabbing through the page reaches every robot title as a link.
+- The same robot in a Teaser block renders the same title, description, and charge bar as it does in the fleet listing.
+- If—and only if—you enabled Block Model v3, a Slate block renders inside a `.block-inner-container` and looks the same in edit mode as in view mode. With the flag left at `2`, as the Robotarium leaves it, every block still renders through the v2 path.
+
+## Further Reading
+
+- [Swap structural components](https://volto-light-theme.readthedocs.io/how-to-guides/swap-structural-components.html)
+- [Slots reference](https://volto-light-theme.readthedocs.io/reference/slots.html)
+- [Card primitive reference](https://volto-light-theme.readthedocs.io/reference/card.html)
+- [Summary components](https://volto-light-theme.readthedocs.io/how-to-guides/summary.html)
+- [Site customization](https://volto-light-theme.readthedocs.io/conceptual-guides/site-customization.html)
+- [Block Model v3](https://volto-light-theme.readthedocs.io/conceptual-guides/block-model-v3.html)
diff --git a/docs/customizing-volto-light-theme/block-development-widgets.md b/docs/customizing-volto-light-theme/block-development-widgets.md
index 2c845b76b..565e2ec60 100644
--- a/docs/customizing-volto-light-theme/block-development-widgets.md
+++ b/docs/customizing-volto-light-theme/block-development-widgets.md
@@ -73,8 +73,8 @@ If you also want the CSS custom properties injected inline, register a `styleFie
```javascript
config.registerUtility({
- name: "myColorField",
- type: "styleFieldDefinition",
+ name: 'myColorField',
+ type: 'styleFieldDefinition',
method: (props) => colors,
});
```
@@ -83,18 +83,6 @@ config.registerUtility({
This is the recommended way to use this widget, since it decouples the styles from the CSS and keeps a single source of truth for the color definitions.
```
-### ThemeColorSwatch Widget
-
-Allows selection from configured themes stored in `config.blocks.themes`:
-
-```javascript
-{
- widget: 'themeColorSwatch',
- title: 'Color Theme',
- colors: config.blocks.themes,
-}
-```
-
### ObjectList Widget
Allows introducing a list of ordered objects with drag and drop:
@@ -136,16 +124,27 @@ Given an array of color definitions, it displays the colors that editors can cho
### Size Widget
-Selects the block size from a default list of values, one of either `small`, `medium`, or `large`:
+Selects the block size from a default list of three values.
+The stored values are the tokens `s`, `m`, and `l`. The names Small, Medium, and Large are only their labels, so a `default` must be one of the tokens:
```javascript
{
widget: 'size',
title: 'Size',
- default: 'medium',
+ default: 'm',
}
```
+VLT maps each token to the `--media-size` custom property through `config.blocks.sizes`, resolved by the `size:noprefix` style field:
+
+```typescript
+config.blocks.sizes = [
+ { style: { '--media-size': 'var(--size-small)' }, name: 's', label: 'Small' },
+ { style: { '--media-size': 'var(--size-medium)' }, name: 'm', label: 'Medium' },
+ { style: { '--media-size': 'var(--size-large)' }, name: 'l', label: 'Large' },
+];
+```
+
Like the BlockAlignment widget, it is based on the Buttons component under the hood, so its actions and the styles they apply are configurable.
### SoftText and SoftTextarea Widgets
@@ -173,10 +172,11 @@ seo_title = schema.TextLine(
### ColorContrastChecker Component
Not a widget itself, but a component that calculates the contrast ratio between two colors following the WCAG accessibility guidelines.
+It is provided by VLT, at `@kitconcept/volto-light-theme/components/Widgets/ColorContrastChecker`.
Add it after a color input field in your own widget to warn the editor in real time about insufficient contrast:
```jsx
-import ContrastChecker from "./ContrastChecker";
+import ContrastChecker from '@kitconcept/volto-light-theme/components/Widgets/ColorContrastChecker';
const MyColorWidget = (props) => {
return (
@@ -196,12 +196,12 @@ The pairings and their defaults are defined in `config.settings.colorMap`:
```javascript
config.settings.colorMap = {
primary_color: {
- colorPair: "primary_foreground_color",
- default: "#ffffff",
+ colorPair: 'primary_foreground_color',
+ default: '#ffffff',
},
primary_foreground_color: {
- colorPair: "primary_color",
- default: "#000000",
+ colorPair: 'primary_color',
+ default: '#000000',
},
};
```
@@ -213,70 +213,74 @@ The BlockAlignment and Size widgets are built on top of it.
You can pass it a configurable list of `actions`, along with the icon and the i18n message used for each one in `actionsInfoMap`, and filter out the default actions you don't want with `filterActions`.
```{note}
-As of VLT 8.0.0-alpha.5 these components were moved to the Volto core package.
-If you are on Volto 19.0.0-alpha.12 or later, use the ones from Volto core instead of the ones provided by VLT.
+As of VLT 8.0.0-alpha.5, four of these components live in Volto core rather than in VLT: `ButtonsWidget`, `BlockAlignment`, `BlockWidth`, and `Size`.
+If you are on Volto 19.0.0-alpha.12 or later, import them from `@plone/volto/components/manage/Widgets/` instead of from VLT.
+
+`ColorContrastChecker` was not part of that move and is still provided by VLT.
```
-## Creating a Custom Hero Block
+(cover-block-label)=
-Let's build a hero block step by step, starting with a basic implementation and then enhancing it with VLT widgets.
+## Creating a Custom Cover Block
+
+Let's build a cover block step by step, starting with a basic implementation and then enhancing it with VLT widgets.
### Step 1: Create Basic Block Schema
-Create `src/components/blocks/myHero/schema.ts`:
+Create `src/components/blocks/Cover/schema.ts`:
```typescript
-import { defineMessages } from "react-intl";
+import { defineMessages } from 'react-intl';
const messages = defineMessages({
- hero: {
- id: "Hero",
- defaultMessage: "Hero",
+ cover: {
+ id: 'Cover',
+ defaultMessage: 'Cover',
},
title: {
- id: "Title",
- defaultMessage: "Title",
+ id: 'Title',
+ defaultMessage: 'Title',
},
subtitle: {
- id: "Subtitle",
- defaultMessage: "Subtitle",
+ id: 'Subtitle',
+ defaultMessage: 'Subtitle',
},
backgroundImage: {
- id: "Background Image",
- defaultMessage: "Background Image",
+ id: 'Background Image',
+ defaultMessage: 'Background Image',
},
});
-const heroBlockSchema = (props) => {
+const coverBlockSchema = (props) => {
const { intl } = props;
return {
- title: intl.formatMessage(messages.hero),
+ title: intl.formatMessage(messages.cover),
fieldsets: [
{
- id: "default",
- title: "Default",
- fields: ["title", "subtitle"],
+ id: 'default',
+ title: 'Default',
+ fields: ['title', 'subtitle'],
},
{
- id: "design",
- title: "Design",
- fields: ["backgroundImage"],
+ id: 'design',
+ title: 'Design',
+ fields: ['backgroundImage'],
},
],
properties: {
title: {
title: intl.formatMessage(messages.title),
- type: "string",
+ type: 'string',
},
subtitle: {
title: intl.formatMessage(messages.subtitle),
- type: "string",
+ type: 'string',
},
backgroundImage: {
title: intl.formatMessage(messages.backgroundImage),
- widget: "object_browser",
- mode: "image",
+ widget: 'object_browser',
+ mode: 'image',
allowExternals: false,
},
},
@@ -284,36 +288,58 @@ const heroBlockSchema = (props) => {
};
};
-export { heroBlockSchema };
+export { coverBlockSchema };
```
### Step 2: Create View Component
-Create `src/components/blocks/myHero/View.tsx`:
+Before writing any markup, note what the block view is **not** responsible for.
+Volto already wraps every block, and under Block Model v3 it wraps it twice:
+
+```html
+