+ ...your markup starts here...
+```
+
+The outer element carries the block's identity and its theme; the inner container carries the width and the alignment.
+This split is what lets a block have a background that behaves independently of its content width, and it is described in full in {ref}`the two-container system
`.
+
+A view that always renders its own `div.block` would be wrapped twice under v3.
+`BlockWrapper` from `@kitconcept/volto-bm3-compat` avoids that.
+It renders the outer element, and an optional inner element that you pass as `ExtraWrapper`, only when the block uses v2.
+With it, the view produces the same structure under both models.
+
+Create {file}`src/components/blocks/Cover/View.tsx`:
+
+```tsx
+import config from '@plone/volto/registry';
+import { BlockWrapper } from '@kitconcept/volto-bm3-compat';
+import type { BlockViewProps } from '@plone/types';
+import type { ReactNode } from 'react';
+
+// Under Block Model v2, BlockWrapper renders `.block.cover` and then this
+// inner container. Under v3 it renders neither, because VLT's renderer adds
+// both. Either way the result is `.block.cover > .block-inner-container > ...`.
+const InnerContainer = (props: { children: ReactNode }) => (
+ {props.children}
+);
+
+const CoverView = (props: BlockViewProps) => {
+ const { title, subtitle, backgroundImage } = props?.data || {};
+ const imageItem = backgroundImage?.[0];
+ const Image = config.getComponent('Image').component;
+
+ return (
+
+ {imageItem?.['@id'] && (
+
+
+
+ )}
+
+
+ {title &&
{title}
}
+ {subtitle &&
{subtitle}
}
+
+
+ );
+};
+
+export default CoverView;
+```
+
+The view renders the image with Volto's `Image` component, which picks a suitable image scale for the space available.
+
+### Step 3: Create Edit Component
+
+Create {file}`src/components/blocks/Cover/Edit.tsx`:
+
+```tsx
+import { useIntl } from 'react-intl';
+import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal';
+import { BlockDataForm } from '@plone/volto/components/manage/Form';
+import { coverBlockSchema } from './schema';
+import CoverView from './View';
+import type { BlockEditProps } from '@plone/types';
+
+const CoverEdit = (props: BlockEditProps) => {
+ const { selected, onChangeBlock, block, data } = props;
+ const intl = useIntl();
+
+ return (
+ <>
+
+
+ {
+ onChangeBlock(block, {
+ ...data,
+ [id]: value,
+ });
+ }}
+ />
+
+ >
+ );
+};
+
+export default CoverEdit;
+```
+
+`BlockDataForm` applies the block's `schemaEnhancer` from its configuration to the schema you pass, which is what makes the styling fields from Step 6 appear in the sidebar.
+
+### Step 4: Register the Basic Block
+
+Edit {file}`src/config/blocks.ts`. Do not replace the file: it already holds the block themes from the previous chapter.
+Add the imports at the top of the file, and the registration inside the existing `install` function:
+
+```typescript
+import type { ConfigType } from '@plone/registry';
+import CoverView from '../components/blocks/Cover/View';
+import CoverEdit from '../components/blocks/Cover/Edit';
+import { coverBlockSchema } from '../components/blocks/Cover/schema';
+import coverSVG from '@plone/volto/icons/hero.svg';
+
+export default function install(config: ConfigType) {
+ // ... block themes configuration ...
+
+ // Register Cover Block
+ config.blocks.blocksConfig.cover = {
+ id: 'cover',
+ title: 'Cover',
+ icon: coverSVG,
+ group: 'common',
+ view: CoverView,
+ edit: CoverEdit,
+ restricted: false,
+ mostUsed: true,
+ blockSchema: coverBlockSchema,
+ sidebarTab: 1,
+ };
+
+ return config;
+}
+```
+
+```{note}
+Give project blocks their own key, and follow the convention every other block in the registry uses: a lowercase single word such as `teaser`, `banner`, or `slider`, reserving camelCase for genuinely multi-word names like `gridBlock`.
+
+The icon is a separate matter. Icon assets are not tied to block names, and {file}`hero.svg` is the one that depicts this layout, so this example uses it.
+```
+
+### Step 5: Add Basic Block Styles
+
+The block has no styling fields yet, so this step sets up **structure only**.
+Everything that depends on a widget—the width, the alignment, the theme color—arrives in Step 8, once the fields that produce those properties exist.
+
+Create {file}`src/theme/blocks/_cover.scss`:
+
+```scss
+.block.cover {
+ .block-inner-container {
+ position: relative;
+ display: flex;
+ overflow: hidden;
+ min-height: 60vh;
+ max-width: var(--default-container-width);
+ align-items: center;
+ padding: 4rem 2rem;
+ margin-inline: auto;
+ }
+
+ .cover-text {
+ // Above the background image.
+ position: relative;
+ z-index: 1;
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ gap: 1rem;
+
+ .cover-title {
+ margin-bottom: $spacing-small;
+ font-size: 5rem;
+ line-height: 1.1;
+ }
+
+ .cover-subtitle {
+ margin-bottom: $spacing-small;
+ font-size: 2rem;
+ line-height: 1.3;
+ opacity: 0.9;
+ }
+ }
+
+ &:has(.cover-image-wrapper) {
+ color: #fff;
+ }
+
+ .cover-image-wrapper {
+ position: absolute;
+ z-index: 0;
+ inset: 0;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ opacity: 0.7;
+ }
+
+ &::after {
+ position: absolute;
+ background: rgb(0 0 0 / 40%);
+ content: '';
+ inset: 0;
+ }
+ }
+}
+```
+
+Two decisions are worth calling out.
+
+**The width goes on `.block-inner-container`, not on `.block`.**
+That is the two-container split: the outer element is the block's full extent, the inner one is where content is constrained. Putting `max-width` on the outer element instead fights VLT, which already constrains inner containers, and it makes a full-width block impossible.
+
+**`min-height` is load-bearing.**
+The image sits in an absolutely positioned wrapper and contributes no height.
+Without `min-height`, the cover is only as tall as its text, and the image is cropped to that strip.
+
+Import the partial in {file}`src/theme/_main.scss`, next to the other block partials:
+
+```scss
+@import './blocks/cover';
+```
+
+(light-theme-cover-actions-label)=
+
+### Step 6: Add Width and Alignment Fields
+
+Now add width and alignment fields to the block, with a schema enhancer next to the block schema. VLT resolves both fields, as described in {ref}`the previous chapter `.
+
+:::{important}
+The enhancer below **extends** the styling fieldset; it does not create one.
+`schema.properties.styles` only exists once VLT's `defaultStylingSchema` has run, so this enhancer only works when it is composed after it, which is what Step 7 does:
+
+```typescript
+schemaEnhancer: composeSchema(defaultStylingSchema, coverSchemaEnhancer),
+```
+
+Registered on its own, it throws an error, because `schema.properties.styles` is `undefined`.
+This is also why the block gets its **Background color** control for free: that field comes from `defaultStylingSchema`, not from anything you write here.
+:::
+
+Make three changes to {file}`src/components/blocks/Cover/schema.ts`.
+
+First, import the registry at the top of the file:
+
+```typescript
+import config from '@plone/volto/registry';
+```
+
+Second, add two messages to the `messages` object:
+
+```typescript
+ blockWidth: {
+ id: 'Block Width',
+ defaultMessage: 'Block Width',
+ },
+ textAlignment: {
+ id: 'Text Alignment',
+ defaultMessage: 'Text Alignment',
+ },
+```
+
+Third, add the enhancer after `coverBlockSchema`, and export it as well, by replacing the `export` line at the end of the file:
+
+```typescript
+// Schema enhancer that adds the width and alignment styling fields.
+// Only meaningful when composed *after* VLT's `defaultStylingSchema`, which is
+// what creates `schema.properties.styles`.
+const coverSchemaEnhancer = ({ schema, intl }) => {
+ // Add custom fields to the styling schema at the beginning
+ schema.properties.styles.schema.fieldsets[0].fields = [
+ 'align:noprefix',
+ 'blockWidth:noprefix',
+ ...schema.properties.styles.schema.fieldsets[0].fields,
+ ];
+
+ schema.properties.styles.schema.properties['align:noprefix'] = {
+ widget: 'blockAlignment',
+ title: intl.formatMessage(messages.textAlignment),
+ default: 'center',
+ actions: config.blocks.alignments.map((alignment) => alignment.name),
+ };
+
+ schema.properties.styles.schema.properties['blockWidth:noprefix'] = {
+ widget: 'blockWidth',
+ title: intl.formatMessage(messages.blockWidth),
+ default: 'default',
+ actions: config.blocks.widths.map((width) => width.name),
+ };
+
+ return schema;
+};
+
+export { coverBlockSchema, coverSchemaEnhancer };
+```
+
+::::{dropdown} The complete schema.ts after this step
+```typescript
+import { defineMessages } from 'react-intl';
+import config from '@plone/volto/registry';
+
+const messages = defineMessages({
+ cover: {
+ id: 'Cover',
+ defaultMessage: 'Cover',
+ },
+ title: {
+ id: 'Title',
+ defaultMessage: 'Title',
+ },
+ subtitle: {
+ id: 'Subtitle',
+ defaultMessage: 'Subtitle',
+ },
+ backgroundImage: {
+ id: 'Background Image',
+ defaultMessage: 'Background Image',
+ },
+ blockWidth: {
+ id: 'Block Width',
+ defaultMessage: 'Block Width',
+ },
+ textAlignment: {
+ id: 'Text Alignment',
+ defaultMessage: 'Text Alignment',
+ },
+});
+
+const coverBlockSchema = (props) => {
+ const { intl } = props;
+
+ return {
+ title: intl.formatMessage(messages.cover),
+ fieldsets: [
+ {
+ id: 'default',
+ title: 'Default',
+ fields: ['title', 'subtitle'],
+ },
+ {
+ id: 'design',
+ title: 'Design',
+ fields: ['backgroundImage'],
+ },
+ ],
+ properties: {
+ title: {
+ title: intl.formatMessage(messages.title),
+ type: 'string',
+ },
+ subtitle: {
+ title: intl.formatMessage(messages.subtitle),
+ type: 'string',
+ },
+ backgroundImage: {
+ title: intl.formatMessage(messages.backgroundImage),
+ widget: 'object_browser',
+ mode: 'image',
+ allowExternals: false,
+ },
+ },
+ required: [],
+ };
+};
+
+// Schema enhancer that adds the width and alignment styling fields.
+// Only meaningful when composed *after* VLT's `defaultStylingSchema`, which is
+// what creates `schema.properties.styles`.
+const coverSchemaEnhancer = ({ schema, intl }) => {
+ // Add custom fields to the styling schema at the beginning
+ schema.properties.styles.schema.fieldsets[0].fields = [
+ 'align:noprefix',
+ 'blockWidth:noprefix',
+ ...schema.properties.styles.schema.fieldsets[0].fields,
+ ];
+
+ schema.properties.styles.schema.properties['align:noprefix'] = {
+ widget: 'blockAlignment',
+ title: intl.formatMessage(messages.textAlignment),
+ default: 'center',
+ actions: config.blocks.alignments.map((alignment) => alignment.name),
+ };
+
+ schema.properties.styles.schema.properties['blockWidth:noprefix'] = {
+ widget: 'blockWidth',
+ title: intl.formatMessage(messages.blockWidth),
+ default: 'default',
+ actions: config.blocks.widths.map((width) => width.name),
+ };
+
+ return schema;
+};
+
+export { coverBlockSchema, coverSchemaEnhancer };
+```
+::::
+
+Two details in that enhancer decide whether it works at all.
+
+**The field names must end in `:noprefix`.**
+As covered in {ref}`the previous chapter `, `align:noprefix` and `align` are different field names.
+VLT registers its style definitions under the literal names `align:noprefix` and `blockWidth:noprefix`, so a field called `align` matches none of them.
+The `--block-alignment` property is then never injected, and the styles in Step 8 that read it have no effect.
+The mistake is easy to miss: the buttons still appear in the sidebar, but the text does not move.
+
+**Pass the token names as `actions`.**
+Without `actions`, the widgets fall back to Volto's default list, and each default entry carries its own style object, such as `{ '--block-width': 'unset' }` for Full.
+When the chosen entry has a style object, the widget stores that object instead of the token.
+The block would then save a style object rather than `"full"`, skip VLT's definitions in `config.blocks.widths`, and get the class `has--block-width--[object Object]`.
+Mapping `config.blocks.alignments` and `config.blocks.widths` to their names makes the widgets store tokens, and keeps the block in step with the theme, including any width or alignment your project adds.
+
+### Step 7: Update Block Registration with Schema Enhancer
+
+Back in {file}`src/config/blocks.ts`, add the two new imports and the `schemaEnhancer` key to the registration you wrote in Step 4:
+
+```typescript
+import type { ConfigType } from '@plone/registry';
+import CoverView from '../components/blocks/Cover/View';
+import CoverEdit from '../components/blocks/Cover/Edit';
+import {
+ coverBlockSchema,
+ coverSchemaEnhancer,
+} from '../components/blocks/Cover/schema';
+import { composeSchema } from '@plone/volto/helpers/Extensions';
+import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema';
+import coverSVG from '@plone/volto/icons/hero.svg';
+
+export default function install(config: ConfigType) {
+ // ... block themes configuration ...
+
+ // Register Cover Block
+ config.blocks.blocksConfig.cover = {
+ id: 'cover',
+ title: 'Cover',
+ icon: coverSVG,
+ group: 'common',
+ view: CoverView,
+ edit: CoverEdit,
+ restricted: false,
+ mostUsed: true,
+ blockSchema: coverBlockSchema,
+ schemaEnhancer: composeSchema(defaultStylingSchema, coverSchemaEnhancer),
+ sidebarTab: 1,
+ };
+
+ return config;
+}
+```
+
+`composeSchema` runs its arguments in order, so `defaultStylingSchema` creates the styling fieldset and `coverSchemaEnhancer` then extends it. Reverse them and the second enhancer runs against a schema that has no `styles` property yet.
+
+(light-theme-third-party-styling-label)=
+
+:::{tip}
+The same technique adds VLT's theming to a block **you did not write**—one from a third-party add-on, or a core Volto block.
+The only difference is that such a block may already have a `schemaEnhancer` of its own, which you must preserve.
+`composeSchema` skips an enhancer that is `undefined`, so this works whether or not the block has one:
+
+```typescript
+config.blocks.blocksConfig. = {
+ ...config.blocks.blocksConfig.,
+ schemaEnhancer: composeSchema(
+ config.blocks.blocksConfig..schemaEnhancer,
+ defaultStylingSchema,
+ ),
+};
+```
+:::
+
+### Step 8: Update Styles to Use Widget Values
+
+The schema enhancers from Steps 6 and 7 now provide the custom properties that the styles can read: `--block-width` and `--block-alignment` from the new fields, and the theme properties, such as `--theme-color`, from the **Background color** control.
+
+Replace the whole of {file}`src/theme/blocks/_cover.scss` with the following version.
+Compared with Step 5, it adds the five numbered changes:
+
+```scss
+// 1. Lift VLT's layout-width cap for this block. See the explanation below.
+#page-document .blocks-group-wrapper > .block.cover {
+ max-width: 100%;
+}
+
+.block.cover {
+ // 2. The text color of the selected theme.
+ color: var(--theme-foreground-color);
+
+ .block-inner-container {
+ position: relative;
+ display: flex;
+ overflow: hidden;
+ min-height: 60vh;
+
+ // 3. The width chosen in Block Width, with the site default as a
+ // fallback, so the block stays sane if the field is ever removed.
+ max-width: var(--block-width, var(--default-container-width));
+ align-items: center;
+ padding: 4rem 2rem;
+ margin-inline: auto;
+
+ // 4. The background of the selected theme. Use `background`, not
+ // `background-color`, so that gradient themes work as well.
+ background: var(--theme-color);
+ }
+
+ .cover-text {
+ // Above the background image.
+ position: relative;
+ z-index: 1;
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+
+ // 5. The alignment chosen in Text Alignment.
+ align-items: var(--block-alignment, start);
+ gap: 1rem;
+ text-align: var(--block-alignment, start);
+
+ .cover-title {
+ margin-bottom: $spacing-small;
+ font-size: 5rem;
+ line-height: 1.1;
+ }
+
+ .cover-subtitle {
+ margin-bottom: $spacing-small;
+ font-size: 2rem;
+ line-height: 1.3;
+ opacity: 0.9;
+ }
+ }
+
+ &:has(.cover-image-wrapper) {
+ color: #fff;
+ }
+
+ .cover-image-wrapper {
+ position: absolute;
+ z-index: 0;
+ inset: 0;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ opacity: 0.7;
+ }
+
+ &::after {
+ position: absolute;
+ background: rgb(0 0 0 / 40%);
+ content: '';
+ inset: 0;
+ }
+ }
+}
+```
+
+The first rule is needed because VLT limits every block inside a block group to the layout width, with the selector `#page-document .blocks-group-wrapper > *`.
+Lifting that limit for the Cover lets a Full Width cover reach the edges of the page, and the inner container's `max-width` then decides the actual width.
+
+The image and the text both sit inside `.block-inner-container`, so the Block Width control resizes them together.
+The theme color behind the Cover still spans the whole width of the page, because VLT groups consecutive blocks that share a theme and paints the theme on the group.
+Change 4 repeats the theme background inside the Cover, where it shows through the partly transparent image.
+For why gradient themes need `background`, see {ref}`the warning about gradient palettes `.
+
+## Checkpoint
+
+Restart the frontend, then add a Cover block to the Robotarium landing page. Give it the title **Book a robot. Build something.**, a subtitle such as *Twelve units, one afternoon at a time*, and a workshop photo as the background image. Confirm that:
+
+- The block appears in the block chooser, listed as **Cover**.
+- The **Styling** section of the block settings shows the **Text Alignment**, **Block Width**, and **Background color** controls.
+- Changing the alignment moves the title and subtitle, and changing the width resizes the image and the text. If the controls appear but nothing moves, check that the field names end in `:noprefix`.
+- On the published page, setting the width to **Full Width** takes the image from edge to edge.
+
+```{seealso}
+A block can also offer more than one rendering of the same data, as the Listing block does with its variations.
+That is covered in the next chapter, where the Robotarium gets a **Robot Fleet** listing variation with its own card layout and per-item actions.
+```
+
+## Further Reading
+
+- [Widgets reference](https://volto-light-theme.readthedocs.io/reference/widgets.html)
+- [Develop add-ons for VLT](https://volto-light-theme.readthedocs.io/how-to-guides/develop-add-ons.html)
diff --git a/docs/customizing-volto-light-theme/concepts.md b/docs/customizing-volto-light-theme/concepts.md
index 91eba4414..ef8675e61 100644
--- a/docs/customizing-volto-light-theme/concepts.md
+++ b/docs/customizing-volto-light-theme/concepts.md
@@ -1,35 +1,47 @@
---
myst:
html_meta:
- "description": "Concepts"
- "property=og:description": "Concepts"
- "property=og:title": "Concepts"
+ "description": "Foundation, Concepts & Project Setup"
+ "property=og:description": "Foundation, Concepts & Project Setup"
+ "property=og:title": "Foundation, Concepts & Project Setup"
"keywords": "Plone, Volto, Training, Volto Light Theme"
---
-# Volto Light Theme Concepts
+# Foundation, Concepts & Project Setup
-Volto Light Theme (VLT) is a customizable theme built for the Volto frontend of the Plone CMS. It provides a foundation that aims to solve many common design challenges, while remaining flexible enough for customization. It's particularly valuable because it is based on real-world experience, while simultaneously embodying the Volto vision for the future. This module will help you understand the core concepts in VLT and create a mental map of its parts.
+## The Project
-## Base Styling
+Rather than theme an abstract site, this training builds one: **the Robotarium**, a neighborhood workshop that lends robots the way a library lends books. Members browse the fleet, check what is charged and available, and book a unit for the afternoon.
-VLT is designed with simplicity and a minimal aesthetic in mind. Consistency, accessibility, and intuitiveness are what drive the development and improvements for VLT.
+Every VLT feature the training covers earns its place by solving something the Robotarium actually needs:
-## Customizable Variables
+| The site needs | You will learn | Covered in |
+| --- | --- | --- |
+| A look that is its own, not the default | Design tokens, the color system, and block themes | {doc}`design-system-implementation` |
+| A full-width opening on the landing page | Building a custom block with VLT's widgets | {doc}`block-development-widgets` |
+| A fleet listing showing charge levels | Summary components and listing variations | {doc}`advanced-components-bm3` |
+| A booking button on each robot | The Card primitive and its Actions slot | {doc}`advanced-components-bm3` |
+| A newsletter sign-up at the top of the footer | Slots | {doc}`advanced-components-bm3` |
-VLT offers a set of CSS custom properties (variables) that allow developers to customize various design elements, such as:
+The project is called `robotarium`, and Cookieplone names its frontend add-on `volto-robotarium`.
+If you would rather build something else, every step works the same with your own names substituted.
-- Colors
-- Spatial relationships
-- Layouts
+## Volto Light Theme Core Concepts
-These variables can be easily overridden in your project to match the desired visual identity.
+Volto Light Theme (VLT) is a customizable theme for the Volto frontend of Plone.
+It provides a foundation that solves many common design challenges, while remaining flexible enough for customization.
+It is based on real-world projects, and it follows the direction that Volto is taking.
-## Colors
+Most of VLT's design decisions are CSS custom properties that your project can override: the color pairs, the container widths, and the palettes that editors apply to blocks.
+This section introduces each of them, so that you have a mental map of VLT's parts before you start building.
-The color system is designed so that colors work in couples: a "background color" and a "foreground color". The "foreground color" is often called "text color" in other systems, but since we want to use this value for more than text—like icons or borders, this works better as a generic term. Colors that do not specify "foreground" in the name are meant to be background colors.
+### Color System
-The main color properties for a project using VLT are the following:
+The color system is designed so that colors work in pairs: a background color and a foreground color.
+Other systems often call the foreground color the text color, but VLT uses it for more than text, such as icons and borders, so it uses the more generic term.
+Colors that do not have "foreground" in their name are background colors.
+
+The main color properties for a project using VLT are:
```scss
--primary-color: #fff;
@@ -42,20 +54,42 @@ The main color properties for a project using VLT are the following:
--accent-foreground-color: #000;
```
-### Semantic color properties
+These six properties are not declared as plain custom properties.
+VLT registers them with the CSS [`@property`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@property) at-rule, which declares their type and their default value in one place:
+
+```scss
+@property --primary-color {
+ inherits: true;
+ initial-value: #fff;
+ syntax: '';
+}
+```
+
+Registering them has three consequences that matter for a theme:
-As an additional layer on top of the main color properties, we have set in place some semantic custom properties for the basic layout sections. As a default they use the values from the main color variables, but they can be detached if desired by setting new color values. However, we feel that leaving these color relationships as they are helps create a cohesive final design. The semantic layer of properties includes the following:
+- The `initial-value` is the default, so the property always has a valid color, even when nothing sets it. For the same reason, a fallback such as `var(--accent-color, teal)` is never used.
+- The `` syntax makes the value typed, so an invalid value is rejected instead of silently cascading.
+- The browser can animate a typed property, so a transition from one color to another is smooth instead of jumping.
+
+VLT registers `--background` the same way, with white as its default, and uses it as the default value of `--primary-color`.
+
+You override these properties the same way as any other custom property, by assigning to them in `:root`.
+
+### Semantic Color Properties
+
+On top of the main color properties, VLT declares semantic properties for the main layout sections.
+By default they take their values from the main color properties.
+You can set them to other colors, but keeping these relationships helps create a cohesive design:
```scss
// Header
--header-background: var(--primary-color);
---header-foreground: var(--primary-foreground-color);
-//Footer
+// Footer
--footer-background: var(--secondary-color);
--footer-foreground: var(--secondary-foreground-color);
-// Fat Menu
+// Fat menu
--fatmenu-background: var(--accent-color);
--fatmenu-foreground: var(--accent-foreground-color);
@@ -68,35 +102,109 @@ As an additional layer on top of the main color properties, we have set in place
--search-foreground: var(--accent-foreground-color);
// Link color
---link-foreground-color: var(--link-color);
+--link-color: #0070a2;
```
-## Block width
-
-VLT now uses a standard block width definition as well. Currently, this is done via a `widget` component, `BlockWidthWidget`, that will be ported to Volto core in the near future. This component stores the value of the custom property `--block-width` so that it can be used by the StyleWrapper when injecting styles into the markup.
+VLT does not declare `--header-foreground` by default.
+The navigation items fall back to `--primary-foreground-color`, and the {ref}`theme behavior ` sets `--header-foreground` when an editor picks a navigation text color.
-The three-width layout system considers the following variables:
+Links are the one exception to the pattern above.
+VLT declares `--link-color`, but does not use it by default.
+Instead, links use `--link-foreground-color` when it is set, and otherwise the foreground color of the block theme they sit in, `--theme-foreground-color`.
+To give links a distinct color everywhere, set `--link-foreground-color` in your project:
```scss
---layout-container-width: #{$layout-container-width}; // for major elements like headers, & large Blocks like the `volto-slider-block`.
---default-container-width: #{$default-container-width}; // a balanced content presentation for most Blocks.
---narrow-container-width: #{$narrow-container-width}; // optimal readability.
+:root {
+ // Turn links into a different color than the theme foreground color
+ --link-foreground-color: var(--link-color);
+}
```
-The default values map the container width SCSS variables, which have existed in the VLT ecosystem since versions < 6.0.0-alpha.0:
+### Block Themes
+
+VLT includes a block theme system that lets individual blocks use their own color palette.
+The themes are configured in `config.blocks.themes`, and applied through the StyleWrapper when a block renders.
+
+**The four core block theme variables:**
+
+- `--theme-color`: primary background color, the most visible color in the block
+- `--theme-high-contrast-color`: secondary background color for nested elements, such as cards within a block, to separate them from the main background
+- `--theme-foreground-color`: default text and icon color
+- `--theme-low-contrast-foreground-color`: subdued text color for secondary content, such as placeholders or helper text
+
+A theme can also set other CSS properties, but these four variables are the color foundation of each theme.
+
+**Example configuration:**
+
+```typescript
+config.blocks.themes = [
+ {
+ style: {
+ "--theme-color": "#fff",
+ "--theme-high-contrast-color": "#ecebeb",
+ "--theme-foreground-color": "#000",
+ "--theme-low-contrast-foreground-color": "#555555",
+ },
+ name: "default",
+ label: "Default",
+ },
+ {
+ style: {
+ "--theme-color": "#ecebeb",
+ "--theme-high-contrast-color": "#fff",
+ "--theme-foreground-color": "#000",
+ "--theme-low-contrast-foreground-color": "#555555",
+ },
+ name: "grey",
+ label: "Grey",
+ },
+];
+```
+
+Editors select a block theme from the **Background color** control, in the **Styling** section of the block settings in the sidebar.
+VLT's `defaultStylingSchema` adds a `theme` field to the block's schema and renders it with the `color_picker` widget, which draws one swatch per configured theme, colored with that theme's `--theme-color`.
+The block stores the name of the selected theme in its `theme` field.
+
+```{note}
+`color_picker` is a legacy name for what is really a swatch picker.
+VLT registers the same `ColorSwatch` component under both `color_picker` and `colorSwatch`, and a comment in VLT's source notes that the widget should be renamed.
+
+Do not confuse it with two similarly named widgets:
+
+- `colorPicker` is a different widget, a free color input with a contrast checker, used for the site-wide colors that the backend behaviors expose.
+- `themeColorSwatch` is registered by VLT, but currently no schema that VLT ships uses it.
+```
+
+### Container Width System
+
+VLT uses three container widths:
```scss
-// Container widths
-$layout-container-width: 1440px !default;
-$default-container-width: 940px !default;
-$narrow-container-width: 620px !default;
+// Three-width layout system
+--layout-container-width: 1440px; // for major elements like headers and large blocks
+--default-container-width: 940px; // balanced content presentation for most blocks
+--narrow-container-width: 620px; // optimal readability for text
```
-## Block alignment
+VLT declares these three custom properties from SCSS variables of the same name, such as `$default-container-width`.
+To change them, set the SCSS variables, as explained in {ref}`light-theme-custom-properties-label`.
+
+Those three are the site-wide scale. Individual blocks never read them directly. Instead, each block carries a `--block-width` property that points at one of them, and the `blockWidth` widget is what chooses the target:
-As part of the effort to generalize behaviors, another VLT `widget` is the `BlockAlignmentWidget`, which also takes advantage of the StyleWrapper by setting the `--block-alignment` property.
+| Token stored | `--block-width` becomes | Resulting width |
+| --- | --- | --- |
+| `narrow` | `var(--narrow-container-width)` | 620px |
+| `default` | `var(--default-container-width)` | 940px |
+| `layout` | `var(--layout-container-width)` | 1440px |
+| `full` | `100%` | edge to edge |
-The three default options are:
+The indirection is what makes the scale worth having. A block's stylesheet only ever writes `max-width: var(--block-width)`, so it never needs to know which of the three it was handed, and changing `--default-container-width` in one place moves every block set to `default`. Only `full` steps outside the scale, which is why it resolves to a plain `100%` rather than to a container width.
+
+The section on style fields below explains how the stored token becomes that value.
+
+### Block Alignment
+
+The `blockAlignment` widget works the same way, storing one of `left`, `center`, or `right` and resolving it to `--block-alignment`. The three tokens map to these values:
```scss
--align-left: start;
@@ -104,6 +212,462 @@ The three default options are:
--align-right: end;
```
-## Conclusion
+(light-theme-style-fields-label)=
+
+### Style Fields and the `:noprefix` Convention
+
+Width and alignment are both **style fields**, and every style field follows the same two-step process: store a token, then resolve that token to a style object at render time.
+
+What a block actually saves is short. A block set to narrow width and left alignment stores this in its `styles` object:
+
+```json
+{
+ "blockWidth:noprefix": "narrow",
+ "align:noprefix": "left"
+}
+```
+
+Only the tokens are stored—no CSS, no custom property values.
+This relies on the widget storing the token rather than a style object, which {ref}`the Cover block ` in chapter 3 shows how to ensure.
+Resolution happens when the block renders:
+
+1. For each key in `styles`, VLT looks up a `styleFieldDefinition` utility registered under **that exact key**.
+2. The utility returns a list of style definitions. For `blockWidth:noprefix` that list is `config.blocks.widths`.
+3. VLT finds the definition whose `name` matches the stored token and takes its `style` object. For `narrow`, that is `{ '--block-width': 'var(--narrow-container-width)' }`.
+4. The StyleWrapper injects that object as an inline style on the block.
+
+Storing tokens rather than values is what makes the system re-themeable. Change what `narrow` means in `config.blocks.widths` and every block already set to `narrow` follows, because the content only ever recorded the word.
+
+Separately, the StyleWrapper turns each style field into a CSS class.
+By default it prefixes the class with the field name, so a field named `align` with the value `center` produces `has--align--center`.
+A field name ending in `:noprefix` opts out of that prefixing, so `align:noprefix` with the value `center` produces the class `center`.
+
+VLT uses this suffix for its own style fields, and registers the matching style definitions under the same literal name:
+
+```typescript
+config.registerUtility({
+ name: 'align:noprefix',
+ type: 'styleFieldDefinition',
+ method: ({ data }) =>
+ config.blocks.blocksConfig?.[data?.['@type'] ?? '']?.alignments ||
+ config.blocks.alignments,
+});
+```
+
+The suffix is part of the field name, not decoration.
+A field named `align` and a field named `align:noprefix` are two different fields: only the second one matches the utility above, and only the second one gets its CSS custom properties injected.
+The three style fields VLT defines this way are `align:noprefix`, `blockWidth:noprefix`, and `size:noprefix`.
+
+(light-theme-generated-classes-label)=
+
+### Generated Block Classes
+
+Beyond the style fields, VLT adds a set of functions to `config.settings.styleClassNameExtenders`.
+They inspect each block's position in the page and inject extra classes on it.
+This is what lets the theme control vertical spacing between blocks from CSS alone, without a block needing to know anything about its neighbors.
+
+The classes available on every block are:
+
+| Class | Injected when |
+| --- | --- |
+| `next--is--${type}` | there is a following block, and `${type}` is its type |
+| `previous--is--same--block-type` | the preceding block has the same type |
+| `next--is--same--block-type` | the following block has the same type |
+| `is--first--of--block-type` | the preceding block has a different type, or there is none |
+| `is--last--of--block-type` | the following block has a different type, or there is none |
+| `has--headline` | the block has a headline, or follows a Heading block |
+| `previous--has--same--backgroundColor` | the preceding block uses the same theme |
+| `next--has--same--backgroundColor` | the following block uses the same theme |
+| `previous--has--different--backgroundColor` | the preceding block uses a different theme |
+| `next--has--different--backgroundColor` | the following block uses a different theme |
+| `has--block-width--${width}` | always, from the `blockWidth:noprefix` field, or `default` when it is empty |
+| `has--block-alignment--${alignment}` | always, from the `align:noprefix` field, or `center` when it is empty |
+| `has--background-color--${theme}` | always, from the block's theme, or `default` when it has none |
+
+When VLT compares themes, a block without a theme, or a missing neighbor, counts as `default`.
+
+You will see these classes throughout VLT's own stylesheets, and in the examples later in this training.
+For example, VLT's listing styles use two of them to remove the space below a Grid listing when the next block is another listing with the same background, so the pair reads as one band of color:
+
+```scss
+.block.listing.grid {
+ &.next--has--same--backgroundColor.next--is--same--block-type {
+ .listing-item:last-child {
+ padding-bottom: 0 !important;
+ border-bottom: none !important;
+ }
+ }
+}
+```
+
+Because the extenders are registered in configuration, a project can add its own to inject any class it needs.
+
+## Create a New Project with Cookieplone
+
+This training uses Cookieplone 2.0 to generate the project.
+If you have not used Cookieplone before, the [Cookieplone guide](https://6.docs.plone.org/install/create-project-cookieplone.html) lists the tools it needs.
+
+From the folder where you keep your projects, run:
+
+```shell
+uvx cookieplone project
+```
+
+The `project` argument selects the **Plone 6 Project** template.
+Without it, Cookieplone first asks you to choose a category and a template.
+
+Cookieplone then asks a series of questions.
+Accept the defaults, except for the project title:
+
+| Question | Default | Answer |
+| --- | --- | --- |
+| Project Title | `Project Title` | `Robotarium` |
+| Project Slug (Used for repository id) | `robotarium` | accept |
+| Python Package Name | `robotarium` | accept |
+| Use Volto as frontend? | Yes | accept |
+| all other questions | | accept |
+
+The title determines the other names.
+Cookieplone derives the slug `robotarium` from it, and uses the slug for the output folder and the Python package name.
+It also derives the name of the frontend add-on, `volto-robotarium`, without asking, and it picks the Plone and Volto versions for you.
+At the time of writing, it generated a project with Plone 6.2.2 and Volto 19.4.1.
+
+```{important}
+Every path in this training of the form {file}`frontend/packages/volto-robotarium/…` uses that add-on name.
+If you build something other than a Robotarium, the add-on is called `volto-` followed by your project slug, and you should adjust the paths accordingly.
+```
+
+You should end up with this shape:
+
+```console
+robotarium/
+├── Makefile
+├── backend/
+│ ├── pyproject.toml
+│ └── src/robotarium/
+└── frontend/
+ ├── mrs.developer.json
+ ├── volto.config.js
+ └── packages/volto-robotarium/ <- your project add-on
+```
+
+## Installing Volto Light Theme
+
+VLT is shipped as two add-ons that you must install together:
+
+- the frontend Volto add-on `@kitconcept/volto-light-theme`, which is also a theme add-on,
+- the backend Plone add-on `kitconcept.voltolighttheme`, which provides the site customization behaviors.
+
+```{note}
+This training uses VLT 8.0.0, which requires Volto 19.
+The [VLT compatibility table](https://volto-light-theme.readthedocs.io/reference/compatibility.html) lists the Volto versions that each VLT version supports.
+```
+
+You first declare both add-ons in your project, and then install everything with a single command.
+
+### Step 1: Declare the Frontend Packages
+
+VLT supports all core blocks, and it also supports blocks from a set of recommended add-ons that provide the basic blocks for your website.
+Including them is not required, and you can pick only the ones you want to use.
+
+VLT does not install the recommended block add-ons for you, so you add them to your project add-on yourself, together with VLT.
+Open {file}`frontend/packages/volto-robotarium/package.json` and add them to its `dependencies`:
+
+```json
+{
+ "dependencies": {
+ "@eeacms/volto-accordion-block": "^12.0.0",
+ "@kitconcept/volto-banner-block": "^1.2.1",
+ "@kitconcept/volto-bm3-compat": "^1.0.0-alpha.1",
+ "@kitconcept/volto-button-block": "^5.0.0",
+ "@kitconcept/volto-carousel-block": "^3.0.0",
+ "@kitconcept/volto-dsgvo-banner": "^4.0.0",
+ "@kitconcept/volto-heading-block": "^2.5.0",
+ "@kitconcept/volto-highlight-block": "^5.0.0",
+ "@kitconcept/volto-introduction-block": "^1.4.1",
+ "@kitconcept/volto-light-theme": "^8.0.0",
+ "@kitconcept/volto-logos-block": "^4.0.0",
+ "@kitconcept/volto-separator-block": "^5.0.0",
+ "@kitconcept/volto-slider-block": "^7.0.0",
+ "@plonegovbr/volto-social-media": "^3.0.0-alpha.0"
+ }
+}
+```
+
+```{note}
+These are the known good versions for VLT 8.0.0. The up-to-date list lives in the [recommended add-ons reference](https://volto-light-theme.readthedocs.io/reference/recommended-addons.html) of the VLT documentation.
+```
+
+`@kitconcept/volto-bm3-compat` is not a block add-on.
+VLT already depends on it, and you list it here because the block you build in chapter 3 imports from it.
+
+Installing a package is not enough: Volto also has to load it as an add-on.
+In the same {file}`package.json`, Cookieplone generated two empty keys, `"addons": []` and `"theme": ""`.
+List the add-ons in `addons`, and declare VLT as the `theme`:
+
+```json
+{
+ "addons": [
+ "@eeacms/volto-accordion-block",
+ "@kitconcept/volto-banner-block",
+ "@kitconcept/volto-bm3-compat",
+ "@kitconcept/volto-button-block",
+ "@kitconcept/volto-carousel-block",
+ "@kitconcept/volto-dsgvo-banner",
+ "@kitconcept/volto-heading-block",
+ "@kitconcept/volto-highlight-block",
+ "@kitconcept/volto-introduction-block",
+ "@kitconcept/volto-logos-block",
+ "@kitconcept/volto-separator-block",
+ "@kitconcept/volto-slider-block",
+ "@plonegovbr/volto-social-media",
+ "@kitconcept/volto-light-theme"
+ ],
+ "theme": "@kitconcept/volto-light-theme"
+}
+```
+
+VLT needs both keys, because it is both a regular add-on and a theme add-on.
+
+```{important}
+VLT must be the last entry in `addons`.
+Volto applies add-ons in the order you list them, and VLT's configuration extends the blocks that the other add-ons register, so it has to run after theirs.
+
+Your project add-on is applied after all of them, because Volto applies the add-ons that an add-on declares before the add-on itself.
+```
+
+### Step 2: Declare the Python Package
+
+Edit {file}`backend/pyproject.toml` and add `kitconcept.voltolighttheme` to the `dependencies` array that Cookieplone generated:
+
+```toml
+dependencies = [
+ "Products.CMFPlone==6.2.2",
+ "plone.api",
+ "plone.restapi",
+ "plone.volto",
+ "kitconcept.voltolighttheme==8.0.0",
+]
+```
+
+Keep the Plone version that Cookieplone generated for you, if it differs from the one shown here.
+
+### Step 3: Install Everything
+
+Both manifests are now edited, so install the whole project in one step, from the project root:
+
+```shell
+make install
+```
+
+This installs the backend and the frontend, and creates a Plone site with the id `Plone`.
+
+### Step 4: Start the Servers and Install VLT in Plone
+
+Start your development environment, from the project root, in two terminals:
+
+```shell
+# Terminal 1 - Backend
+make backend-start
+
+# Terminal 2 - Frontend
+make frontend-start
+```
+
+Once the frontend is running, open http://localhost:3000.
+The first time you open the site, a cookie consent dialog from `@kitconcept/volto-dsgvo-banner` covers the page.
+Choose one of its options to close it.
+
+Then log in at http://localhost:3000/login with the user `admin` and the password `admin`, and install the backend add-on:
+
+1. Go to http://localhost:3000/controlpanel/addons.
+2. In the list of available add-ons, select **Volto Light Theme: Install** to show its details.
+3. Select **Install**.
+
+### Step 5: Activate Behaviors for Plone Site
+
+The backend add-on ships five behaviors.
+None of them is enabled automatically, because each one adds fields to the edit form of the content type you apply it to, and you should opt into the ones you want.
+
+| Behavior | Shown in the Behaviors tab as | What it adds |
+| --- | --- | --- |
+| `voltolighttheme.header` | Header customizations for sites/subsites | Site logo, complementary logo, fat menu switch, intranet header switch, site flag, and site actions |
+| `voltolighttheme.theme` | Theme colors customizations for sites/subsites | Five color fields for the navigation, fat menu, breadcrumbs, and footer. See {ref}`light-theme-behavior-colors-label`. |
+| `voltolighttheme.footer` | Footer customizations for sites/subsites | Footer logos with their size and container width, footer links, and the footer colophon text |
+| `kitconcept.footer` | kitconcept specific footer customizations | The footer layout of kitconcept distributions: a footer logo, an address, three link columns, and a sponsor logo with its link |
+| `kitconcept.sticky_menu` | Sticky menu | Icon links fixed to the right edge of the screen, their colors, and a switch to show them on mobile |
+
+The header fields work as follows:
+
+Site logo
+: The main logo, at the top left of the header.
+
+Complementary logo
+: A second logo on the right side of the header. Only the intranet header shows it.
+
+Fat menu switch
+: The fat menu is the panel that opens below the navigation when you select a main section. It is enabled by default.
+
+Intranet header switch
+: Replaces the default header with a layout intended for intranet sites.
+
+Site flag
+: A short text shown in a colored pill at the top of the header.
+
+Site actions
+: Links shown at the top right of the header, each with a title, a target URL, and an option to open it in a new tab.
+
+To activate the behaviors:
+
+1. Go to http://localhost:3000/controlpanel/dexterity-types/Plone%20Site.
+2. In the **Behaviors** tab, select the behaviors from the table above that you want.
+3. Select **Save**.
+
+This training uses the first three.
+Leave `kitconcept.footer` disabled while you follow it: when it is active, VLT renders kitconcept's footer layout, which sets its own footer background colors and replaces the footer gradient that you define in the next chapter.
+
+The behaviors add their fields to the content type they are applied to.
+Applied to the Plone Site, they configure the whole site.
+Applied to a subsite, they configure that branch instead, and each page uses the settings of its nearest ancestor that has them.
+
+(light-theme-behavior-colors-label)=
+
+#### What the theme behavior does
+
+The behavior adds five empty, optional fields to the **Theming** tab of the edit form, each rendered with the `colorPicker` widget.
+When an editor fills in a field, VLT sets the matching custom property:
+
+| Field | Label in the form | Custom property it sets |
+| --- | --- | --- |
+| `header_foreground` | Navigation Text Color | `--header-foreground` |
+| `accent_foreground_color` | Fat Menu / Breadcrumbs Text Color | `--accent-foreground-color` |
+| `accent_color` | Fat Menu Background Color | `--accent-color` |
+| `secondary_foreground_color` | Footer Font Color | `--secondary-foreground-color` |
+| `secondary_color` | Footer Background Color | `--secondary-color` |
+
+There is no field for the primary color pair.
+The header background follows `--primary-color`, which no field of the behavior sets, and the navigation text color has its own field, `header_foreground`.
+
+VLT writes the values that editors choose into a `:root` rule at the top of the document head.
+Your project's stylesheet comes after that rule, and the design tokens that you will define in the next chapter use the same `:root` selector.
+When both set the same property, your stylesheet wins, and the value that the editor chose has no effect.
+
+```{important}
+Decide who owns each of these five properties.
+If editors should be able to change a color, leave its property out of your stylesheet.
+
+The Robotarium's stylesheet sets `--accent-color`, `--accent-foreground-color`, and `--secondary-color` in the next chapter.
+On the Robotarium, the Fat Menu Background Color, Fat Menu / Breadcrumbs Text Color, and Footer Background Color fields therefore have no effect, while Navigation Text Color and Footer Font Color still work.
+```
+
+```{note}
+Behaviors can be added in new releases, and this table reflects `kitconcept.voltolighttheme` 8.0.0.
+The authoritative list is the behavior registration in the add-on itself, at {file}`backend/src/kitconcept/voltolighttheme/behaviors/configure.zcml` in the [VLT repository](https://github.com/kitconcept/volto-light-theme).
+See the [site customization guide](https://volto-light-theme.readthedocs.io/conceptual-guides/site-customization.html) for what each field does.
+```
+
+Your site now has the VLT site customization fields available.
+
+## File Structure Setup
+
+Set up the recommended file structure in your project add-on's `src` folder.
+Cookieplone already created part of it, so you are filling in the gaps rather than starting from nothing:
+
+```console
+src/
+├── components/ # exists, empty
+│ └── blocks/ # new
+├── config/
+│ ├── settings.ts # exists, sets the default language
+│ └── blocks.ts # new
+├── index.ts # exists, calls installSettings()
+└── theme/ # new
+ ├── blocks/
+ ├── _variables.scss
+ └── _main.scss
+```
+
+From {file}`frontend/packages/volto-robotarium/src`:
+
+```shell
+mkdir -p components/blocks config theme/blocks
+touch config/blocks.ts theme/_variables.scss theme/_main.scss
+```
+
+```{warning}
+Do not overwrite the generated {file}`index.ts` and {file}`config/settings.ts`.
+The generated `settings.ts` sets your site's `defaultLanguage`, and `index.ts` already calls it.
+Every later step in this training that shows one of these two files is showing an **edit**, not a replacement.
+```
+
+Whenever you add new files to your project, restart the frontend, because hot reload does not pick them up.
+
+(light-theme-insertion-points-label)=
+
+### The Two Theme Insertion Points
+
+Two of those filenames are load-bearing.
+Volto scans every add-on for {file}`theme/_variables.scss` and {file}`theme/_main.scss`, and injects whichever it finds into the theme's stylesheet at two different points:
+
+| File | Injected | Use it for |
+| --- | --- | --- |
+| {file}`theme/_variables.scss` | **before** VLT's own variables | the few SCSS variables VLT compiles with |
+| {file}`theme/_main.scss` | **after** all of VLT's styles | everything else, including all your custom properties |
+
+The order is what makes each one useful.
+No other filename is special: `theme/blocks/` and anything else you add are ordinary partials that become part of the build only because {file}`_main.scss` imports them.
+
+```{note}
+Volto skips these two files for whichever add-on is registered as the `theme`.
+They work for {file}`volto-robotarium` precisely because VLT, not your project, is the theme add-on.
+```
+
+(light-theme-custom-properties-label)=
+
+#### Prefer CSS custom properties
+
+Almost all of your theming should be CSS custom properties written in {file}`_main.scss`, not SCSS variables.
+
+VLT is built as a custom-property system: the color pairs, the semantic properties, the container widths, and the block themes are all custom properties, resolved in the browser. That is what lets you override them per block, per section, and at runtime—a block theme works by re-declaring `--theme-color` on one block, which no build-time variable could do. Setting your design tokens as custom properties in `:root` is the normal path, and the next chapter sets these, among others:
+
+```scss
+// src/theme/_main.scss
+:root {
+ --accent-color: #3b5759;
+ --accent-foreground-color: #fff;
+ --link-foreground-color: #157a7a;
+}
+```
+
+{file}`_variables.scss` is the narrow exception, for values VLT **computes with at build time**. VLT declares its SCSS variables with `!default`, so each one applies only when nothing has already set it. Since {file}`_variables.scss` is injected first, anything you assign there wins—and it is the *only* place they can be changed, because VLT's stylesheets have already used the values by the time {file}`_main.scss` is injected:
+
+```scss
+// src/theme/_variables.scss
+$default-container-width: 1120px;
+```
+
+Two kinds of value belong there:
+
+- **Breakpoints**, which cannot be custom properties at all, since media queries cannot read them.
+- **The three container widths.** VLT declares `--layout-container-width`, `--default-container-width`, and `--narrow-container-width` from the SCSS variables of the same name, and also uses the SCSS values in media and container queries. Setting the SCSS variable updates both, so you never set the custom property yourself.
+
+The rule of thumb: custom properties for anything meant to vary per block, per section, or at runtime—which is nearly everything, and all of the color system. SCSS variables only for the handful of values VLT compiles into media queries and mixins.
+
+## Checkpoint
+
+Restart both servers and confirm the install landed before moving on. Most trouble in later chapters traces back to one of these:
+
+- The site at http://localhost:3000 renders in VLT's styling, not Volto's default Pastanaga theme. If it looks unchanged, VLT is listed in `addons` but not declared as the `theme`.
+- Editing a page and selecting a Text block shows a **Background color** control in the **Styling** section of the block settings. This confirms VLT's block configuration is applied.
+- The add-ons control panel lists **Volto Light Theme: Install** among the installed add-ons, and the Plone Site content type shows the `voltolighttheme` behaviors as enabled.
+- Adding a temporary rule to {file}`src/theme/_main.scss`, such as `body { border-top: 4px solid red; }`, shows that border after a restart. If it does not, your add-on is not being picked up, and no amount of CSS in later chapters will apply. Remove the rule afterward.
+
+## Further Reading
+
+This training covers the parts of VLT you need to build a project. The theme's own documentation goes deeper on each topic:
-VLT provides a robust foundation for Plone CMS frontend development through this framework of customizable variables and standardized block controls. Through these core concepts, VLT strikes a balance between maintaining consistency and flexibility, allowing developers to create cohesive designs while still having the freedom to customize elements to match their specific project needs.
+- [Color system](https://volto-light-theme.readthedocs.io/conceptual-guides/color-system.html)
+- [Layout](https://volto-light-theme.readthedocs.io/conceptual-guides/layout.html)
+- [Vertical spacing](https://volto-light-theme.readthedocs.io/conceptual-guides/vertical-spacing.html)
+- [Install guide](https://volto-light-theme.readthedocs.io/how-to-guides/install.html)
+- [Site customization](https://volto-light-theme.readthedocs.io/conceptual-guides/site-customization.html)
diff --git a/docs/customizing-volto-light-theme/creating-new-project.md b/docs/customizing-volto-light-theme/creating-new-project.md
deleted file mode 100644
index 9cc7fef69..000000000
--- a/docs/customizing-volto-light-theme/creating-new-project.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-myst:
- html_meta:
- "description": "Create a new project with Plone and Volto"
- "property=og:description": "Create a new project with Plone and Volto"
- "property=og:title": "Create a new project with Plone and Volto"
- "keywords": "Plone, Volto, Training, Theme, Footer"
----
-
-# Create a new project with Plone and Volto
-We recommend creating your Plone project with **Cookieplone**. Our comprehensive documentation provides step-by-step guidance to help you get started. For detailed installation instructions, visit our [Cookieplone guide](https://6.docs.plone.org/install/create-project-cookieplone.html).
\ No newline at end of file
diff --git a/docs/customizing-volto-light-theme/design-system-implementation.md b/docs/customizing-volto-light-theme/design-system-implementation.md
new file mode 100644
index 000000000..564ea9c6b
--- /dev/null
+++ b/docs/customizing-volto-light-theme/design-system-implementation.md
@@ -0,0 +1,400 @@
+---
+myst:
+ html_meta:
+ "description": "Design system implementation & theming"
+ "property=og:description": "Design system implementation & theming"
+ "property=og:title": "Design system implementation & theming"
+ "keywords": "Plone, Volto, Training, Theme, Footer"
+---
+
+# Design System Implementation & Theming
+
+The Robotarium has a look: workshop teal, a technical font, and surfaces that read like painted metal. This chapter turns that into a working theme.
+
+Nothing here is specific to robots. The steps are the ones you follow for any design handed to you—pull the decisions out of it, express them as tokens, and let VLT's components pick them up.
+
+## Extracting Design Tokens
+
+Before writing any code, list the decisions that the design makes:
+
+- **Color pairs**: the primary, secondary, and accent background colors, and the text color used on each.
+- **Link color**, if links should stand out from the surrounding text.
+- **Typography**: the font families, and any line heights or font weights that differ from VLT's.
+- **Block palettes**: the backgrounds that editors should be able to choose for a section of a page.
+
+For the Robotarium, that list comes down to a dark teal accent with white text, a pale teal secondary color, a teal link color, the Chakra Petch font, and one extra block palette.
+
+## Implementing Your Design System
+
+### Step 1: Add Font Files
+
+If you're using custom fonts, add the font files to your theme directory.
+The Robotarium uses [Chakra Petch](https://fonts.google.com/specimen/Chakra+Petch).
+From {file}`frontend/packages/volto-robotarium/src/theme`, create a folder for it:
+
+```shell
+mkdir -p fonts/Chakra_Petch
+```
+
+Download the font family from Google Fonts, and copy its regular and bold files into that folder:
+
+```console
+src/theme/fonts/Chakra_Petch/
+├── ChakraPetch-Regular.ttf
+└── ChakraPetch-Bold.ttf
+```
+
+Keeping the files inside your theme bundles them with it, so your stylesheets can refer to them with relative paths.
+
+### Step 2: Override SCSS Variables
+
+Set the build-time values in {file}`src/theme/_variables.scss`, as described in {ref}`light-theme-custom-properties-label`:
+
+```scss
+// src/theme/_variables.scss
+$default-container-width: 1120px;
+$spacing-large: 80px;
+```
+
+The Robotarium widens the default container from 940px to 1120px, and increases VLT's large spacing step from 60px to 80px.
+Setting `$default-container-width` also updates `--default-container-width`, so you do not need to set both.
+
+### Step 3: Define Your Design Tokens
+
+In {file}`src/theme/_main.scss`, declare the fonts and the CSS custom properties that carry your design:
+
+```scss
+@font-face {
+ font-family: 'Chakra Petch';
+ src: url('./fonts/Chakra_Petch/ChakraPetch-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Chakra Petch';
+ src: url('./fonts/Chakra_Petch/ChakraPetch-Bold.ttf') format('truetype');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+:root {
+ // Extract these from your design
+ --accent-color: #3b5759;
+ --accent-foreground-color: #fff;
+ --secondary-color: #afcac8;
+
+ // Typography. VLT reads the font through
+ // `$page-font: var(--custom-main-font, $page-font-template)`.
+ --custom-main-font: 'Chakra Petch', sans-serif;
+
+ // Give links a color of their own instead of the theme foreground color
+ --link-foreground-color: #157a7a;
+
+ // Breadcrumbs: a white bar, with text in the footer text color
+ --breadcrumbs-background: var(--background);
+ --breadcrumbs-foreground: var(--secondary-foreground-color);
+
+ // Gradients for header and footer.
+ // `--background` is VLT's base color, white by default.
+ --header-background: linear-gradient(
+ -3deg,
+ var(--background) 0%,
+ color-mix(in oklab, var(--secondary-color) 1%, var(--background)) 20%,
+ color-mix(in oklab, var(--secondary-color) 3%, var(--background)) 35%,
+ color-mix(in oklab, var(--secondary-color) 10%, var(--background)) 50%,
+ color-mix(in oklab, var(--secondary-color) 30%, var(--background)) 65%,
+ color-mix(in oklab, var(--secondary-color) 65%, var(--background)) 80%,
+ color-mix(in oklab, var(--secondary-color) 90%, var(--background)) 92%,
+ var(--secondary-color) 100%
+ );
+
+ --footer-background: radial-gradient(
+ ellipse 100% 100% at 50% 100%,
+ var(--secondary-color) 0%,
+ var(--secondary-color) 20%,
+ color-mix(in oklab, var(--secondary-color) 85%, var(--background)) 30%,
+ color-mix(in oklab, var(--secondary-color) 65%, var(--background)) 40%,
+ color-mix(in oklab, var(--secondary-color) 45%, var(--background)) 50%,
+ color-mix(in oklab, var(--secondary-color) 30%, var(--background)) 60%,
+ color-mix(in oklab, var(--secondary-color) 18%, var(--background)) 70%,
+ color-mix(in oklab, var(--secondary-color) 10%, var(--background)) 80%,
+ color-mix(in oklab, var(--secondary-color) 4%, var(--background)) 90%,
+ var(--background) 100%
+ );
+}
+```
+
+The breadcrumbs tokens replace VLT's defaults, which use the accent colors.
+The Robotarium's accent text color is white, for the teal fat menu, and it would disappear on the white breadcrumbs bar, so the breadcrumbs take the text color of the footer instead.
+As a result, the Footer Font Color field of the theme behavior also changes the color of the breadcrumbs.
+
+The header and footer gradients are assigned to the same `--header-background` and `--footer-background` properties you saw in the previous chapter.
+VLT paints the header and the footer with `background`, not `background-color`, so a gradient works there as well as a flat color, with no component changes.
+
+Setting a font is a good example of the {ref}`prefer custom properties ` rule from the previous chapter: you do not shadow VLT's typography stylesheets, you set the one property they already read.
+
+Site-wide rules that are not tokens go in the same file, outside `:root`:
+
+```scss
+.breadcrumbs {
+ border-bottom: 1px solid var(--secondary-color);
+}
+
+// Let the first block sit flush with the header
+#page-document,
+#page-edit,
+#page-add {
+ .blocks-group-wrapper:first-child {
+ padding-top: 0;
+ }
+}
+```
+
+```{warning}
+Declare custom properties inside `:root`, but keep normal rules outside it.
+Nesting a selector such as `#page-document` inside `:root` compiles to `:root #page-document`, which adds specificity you will have to fight later.
+```
+
+### Step 4: Add Block-Specific Styles
+
+Keep per-block styles in their own partials under {file}`src/theme/blocks/`, one file per block.
+This keeps them easy to find, and stops {file}`_main.scss` from growing too large.
+The Robotarium adds four of them.
+
+The button partial replaces the colors of buttons on hover and focus with the accent colors:
+
+```scss
+// src/theme/blocks/_button.scss
+body .block.__button {
+ > .button.container,
+ > .block-inner-container {
+ a,
+ button {
+ padding: 1rem;
+ transition:
+ background 0.2s ease,
+ color 0.2s ease;
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: var(--accent-color);
+ color: var(--accent-foreground-color);
+ }
+ }
+ }
+}
+```
+
+VLT colors buttons inside themed blocks with rules that start with `body`.
+Starting your rule with `body` as well gives it the same specificity, and it wins because your stylesheet loads after VLT's.
+
+The hover colors also matter for the gradient palette that you add in Step 5.
+VLT's own hover state uses `--theme-color` as the text color.
+A gradient is not a valid text color, so in a section with a gradient palette, the button text would inherit the dark foreground color and disappear against the dark hover background.
+The accent colors avoid that.
+
+The slider and grid partials give text a frosted-glass panel, on the slide titles and in four-column grids:
+
+```scss
+// src/theme/blocks/_slider.scss
+.block.slider .teaser-item .teaser-item-title {
+ background: rgb(255 255 255 / 10%);
+ backdrop-filter: blur(20px) saturate(110%);
+ box-shadow:
+ 0 8px 32px 0 rgb(31 135 125 / 10%),
+ inset 0 0 0 1px rgb(255 255 255 / 10%);
+ color: var(--theme-foreground-color);
+}
+```
+
+```scss
+// src/theme/blocks/_grid.scss
+.block.gridBlock .four .slate:not(.inner) {
+ padding: 2.5rem;
+ padding-top: 4rem;
+ backdrop-filter: blur(20px) saturate(110%);
+ box-shadow:
+ 0 8px 32px 0 rgb(31 135 125 / 10%),
+ inset 0 0 0 1px rgb(255 255 255 / 10%);
+}
+```
+
+The teaser partial adds space around the text of Teaser blocks:
+
+```scss
+// src/theme/blocks/_teaser.scss
+.block.teaser .card .card-inner .card-summary {
+ padding: $spacing-large;
+}
+```
+
+The teaser rule uses `$spacing-large`, one of VLT's SCSS variables, which you set to 80px in Step 2.
+VLT's variables and mixins are in scope for your stylesheets because {file}`_main.scss` is compiled after them, so you can reuse the theme's spacing scale, breakpoints, and typography mixins instead of inventing new values.
+
+(charging-bay-label)=
+
+### Step 5: Configure Block Themes
+
+Block themes are the palettes an editor can choose between on any block. The Robotarium gets two: the plain default, and **Charging Bay**, a soft teal wash for sections that should feel like the lit alcove where the robots dock.
+
+In {file}`src/config/blocks.ts`, define them:
+
+```typescript
+import type { ConfigType } from '@plone/registry';
+
+export default function install(config: ConfigType) {
+ // Block palettes
+ config.blocks.themes = [
+ {
+ style: {
+ '--theme-color': 'white',
+ '--theme-high-contrast-color': '#bbd1d0',
+ '--theme-foreground-color': 'black',
+ '--theme-low-contrast-foreground-color': '#555555',
+ },
+ name: 'default',
+ label: 'Default',
+ },
+ {
+ style: {
+ '--theme-color': `linear-gradient(180deg, oklab(1 0 0 / 0.9) 0%, transparent 15%, transparent 85%, oklab(1 0 0 / 0.9) 100%),
+ radial-gradient(ellipse 850px 700px at 12% 15%, oklab(1 0 0 / 0.4) 0%, transparent 70%),
+ radial-gradient(ellipse 900px 650px at 85% 85%, oklab(1 0 0 / 0.4) 0%, transparent 70%),
+ radial-gradient(ellipse 850px 750px at 70% 35%, oklab(0.805 -0.030 -0.003 / 0.5) 0%, oklab(0.835 -0.025 -0.002 / 0.2) 50%, transparent 85%),
+ radial-gradient(ellipse 920px 800px at 25% 65%, oklab(0.780 -0.032 -0.004 / 0.45) 0%, oklab(0.825 -0.027 -0.003 / 0.2) 50%, transparent 87%),
+ radial-gradient(ellipse 1100px 900px at 50% 50%, oklab(0.805 -0.030 -0.003 / 0.25) 0%, oklab(0.850 -0.025 -0.002 / 0.1) 60%, transparent 90%),
+ linear-gradient(182deg, oklab(1 0 0) 0%, oklab(0.988 -0.006 0) 10%, oklab(0.958 -0.016 -0.001) 22%, oklab(0.910 -0.025 -0.003) 35%, oklab(0.860 -0.030 -0.003) 45%, oklab(0.820 -0.032 -0.004) 52%, oklab(0.860 -0.030 -0.003) 59%, oklab(0.910 -0.025 -0.003) 69%, oklab(0.958 -0.016 -0.001) 82%, oklab(0.988 -0.006 0) 92%, oklab(1 0 0) 100%)`,
+ '--theme-high-contrast-color': 'oklab(1 0 0 / 0.1)',
+ '--theme-foreground-color': 'black',
+ '--theme-low-contrast-foreground-color': '#555555',
+ },
+ name: 'charging-bay',
+ label: 'Charging Bay',
+ },
+ ];
+
+ // Re-point the Grid block at the new palettes. See below.
+ config.blocks.blocksConfig.gridBlock.themes = config.blocks.themes;
+
+ return config;
+}
+```
+
+```{note}
+VLT copies the default palettes to the Grid block when its own configuration runs, which is before yours.
+The Grid block is the only block in VLT, or in any of the recommended add-ons, that keeps its own copy of the palettes, so this is the only place where the extra line is needed.
+```
+
+(light-theme-gradient-themes-label)=
+
+```{warning}
+`--theme-color` is usually a flat color, but Charging Bay sets it to a gradient.
+A gradient only works where a stylesheet reads the property with `background`, as VLT does for block backgrounds.
+Where a stylesheet reads it with `background-color` or `color`, the browser ignores the declaration:
+
+- The Charging Bay swatch in the block settings would look empty, which the rule below fixes.
+- The Button block would lose its hover text color, which the button partial from Step 4 fixes.
+
+In your own styles, read `--theme-color` with `background`, never with `background-color`.
+```
+
+VLT styles the swatches in the **Background color** control with `background-color: var(--theme-color)`.
+Add this rule to {file}`src/theme/_main.scss`, next to the other site-wide rules, to give the Charging Bay swatch a gradient of its own:
+
+```scss
+// Match the theme swatch in the sidebar to the custom "charging bay" theme
+#sidebar {
+ .color-swatch-widget .color-swatch-option-handler.charging-bay {
+ background: linear-gradient(135deg, var(--secondary-color) 0%, #fff 100%);
+ }
+}
+```
+
+### Step 6: Keep the Layout Settings in Sync
+
+Widening the container in SCSS is only half the change.
+VLT also reads the container width from configuration, in {file}`src/config/settings.ts`.
+Add the `layout` block to the file that Cookieplone generated.
+Do not replace the file, or you will drop your language settings:
+
+```typescript
+import type { ConfigType } from '@plone/registry';
+
+export default function install(config: ConfigType) {
+ // ... the language settings generated by Cookieplone ...
+
+ // Added in this step
+ config.settings.layout = {
+ ...config.settings.layout,
+ defaultContainerWidth: 1120,
+ };
+
+ return config;
+}
+```
+
+This value is not used for layout—the CSS custom property does that.
+It is used to compute the `sizes` attribute of responsive images in teasers and listings, which tells the browser how wide an image will actually be, so that it can pick the right scale to download.
+
+If the two values disagree, nothing breaks visibly, but the browser picks image scales for the wrong width.
+When the configured width is too small, images look soft; when it is too large, pages download bigger images than they need.
+Whenever you change `$default-container-width`, change `defaultContainerWidth` to match.
+
+```{note}
+`config.settings.layout` also carries `tabletBreakpoint`, 768 by default, which is used in the same calculation.
+It matches VLT's `$largest-mobile-screen` SCSS variable, so if you change VLT's breakpoints, update it as well.
+```
+
+### Step 7: Main Index Configuration
+
+The generated {file}`src/index.ts` already calls `installSettings`. Add the two lines that wire up your block configuration:
+
+```typescript
+import type { ConfigType } from '@plone/registry';
+import installSettings from './config/settings';
+import installBlocks from './config/blocks';
+
+function applyConfig(config: ConfigType) {
+ installSettings(config);
+ installBlocks(config);
+ return config;
+}
+
+export default applyConfig;
+```
+
+### Step 8: Import Your Stylesheets
+
+{file}`_main.scss` is the entry point that Volto injects after all of VLT's styles.
+Import your partials there, in the order you want them applied:
+
+```scss
+// src/theme/_main.scss
+// ... your @font-face rules, :root tokens, and site-wide rules above ...
+
+@import './blocks/button';
+@import './blocks/grid';
+@import './blocks/slider';
+@import './blocks/teaser';
+```
+
+{file}`_variables.scss` needs no import, because Volto injects it on its own.
+
+## Checkpoint
+
+Restart the frontend and confirm the following before moving on:
+
+- The site renders in Chakra Petch, and the header and footer show their gradients.
+- Links in body text are teal, rather than the color of the surrounding text.
+- Opening a page in edit mode and selecting a Text block shows **Default** and **Charging Bay** in the **Background color** control, and the Charging Bay swatch shows a gradient.
+- Choosing **Charging Bay** for a block gives it the teal wash.
+
+## Further Reading
+
+- [Color system](https://volto-light-theme.readthedocs.io/conceptual-guides/color-system.html)
+- [Colors reference](https://volto-light-theme.readthedocs.io/reference/colors.html)
+- [Layout](https://volto-light-theme.readthedocs.io/conceptual-guides/layout.html)
diff --git a/docs/customizing-volto-light-theme/index.md b/docs/customizing-volto-light-theme/index.md
index 74e398160..e72d036af 100644
--- a/docs/customizing-volto-light-theme/index.md
+++ b/docs/customizing-volto-light-theme/index.md
@@ -14,23 +14,29 @@ myst:
Level
: Intermediate
-Customizing Volto Light Theme aims to provide developers with comprehensive knowledge and practical skills for theming in Plone 6's Volto frontend by using and extending Volto Light Theme.
+This training teaches you how to theme the Volto frontend of Plone 6 with Volto Light Theme (VLT).
+You will learn how VLT's design tokens, block themes, and widgets fit together, and how to extend them with your own blocks, listing variations, slots, and components.
-The goal of this training is to guide developers in understanding and effectively working with Volto Light Theme to create visually compelling and consistent user interfaces. Due to the scope of the content, the training follows a structured "show and tell" approach, presenting concepts and explaining their usage, while also encouraging participants to put their learning into practice.
-
-Participants will explore essential aspects of Volto Light Theme such as setting up theme providers, customizing styles, and understanding how Volto's theming system functions. The training also covers advanced tips, best practices, and lesser-known features to extend Volto Light Theme and create more tailored solutions.
+The training follows a "show and tell" approach.
+Each chapter explains a concept, then applies it to a site you build along the way for **the Robotarium**, a workshop that lends robots the way a library lends books.
This training is best suited for developers who have prior experience with Volto and want to deepen their theming knowledge.
+It was written for the following versions:
+
+- Volto Light Theme 8.0.0, both the frontend package `@kitconcept/volto-light-theme` and the backend package `kitconcept.voltolighttheme`
+- Volto 19.4
+- Plone 6.2
+- Cookieplone 2.0
+
```{toctree}
:caption: Volto Light Theme
:maxdepth: 1
:numbered:
concepts
-creating-new-project
-installing-vlt
-integrate-new-block
-theming
+design-system-implementation
+block-development-widgets
+advanced-components-bm3
question-answer
```
\ No newline at end of file
diff --git a/docs/customizing-volto-light-theme/installing-vlt.md b/docs/customizing-volto-light-theme/installing-vlt.md
deleted file mode 100644
index 32cc59343..000000000
--- a/docs/customizing-volto-light-theme/installing-vlt.md
+++ /dev/null
@@ -1,71 +0,0 @@
----
-myst:
- html_meta:
- "description": "Installing Volto Light Theme"
- "property=og:description": "Installing Volto Light Theme"
- "property=og:title": "Installing Volto Light Theme"
- "keywords": "Plone, Volto, Training, Volto Light Theme"
----
-
-# Installing Volto Light Theme
-
-Follow the steps below to install and configure VLT in your project. VLT provides a clean and modern design with ready-to-use blocks and components.
-
-## Step 1: Install Volto Light Theme
-
-To install VLT, navigate to the {file}`frontend/packages/volto-my-project` folder and run the following command:
-
-```{note}
-For now we'll be using an `alpha` release, so we need to specify the correct version.
-```
-
-```shell
-pnpm install @kitconcept/volto-light-theme@6.0.0-alpha.2
-```
-
-While in your project package folder, add VLT to the `addons` list in your {file}`package.json`, as follows:
-
-```json
-"addons": ["@kitconcept/volto-light-theme"],
-```
-
-## Step 2: install block add-ons
-
-Volto Light Theme comes with several pre-configured add-ons that provide basic blocks for your website. If you'd like to include them, you can add them in the `addons` section in your {file}`package.json`, but this is not required.
-
-Here is the list of recommended addons to install, including VLT, which should be the last element:
-
-```json
-"addons": [
- "@eeacms/volto-accordion-block",
- "@kitconcept/volto-button-block",
- "@kitconcept/volto-heading-block",
- "@kitconcept/volto-highlight-block",
- "@kitconcept/volto-introduction-block",
- "@kitconcept/volto-separator-block",
- "@kitconcept/volto-slider-block",
- "@kitconcept/volto-light-theme"
-],
-```
-
-## Step 3: configure Volto Light Theme as the theme provider
-
-To leverage a cohesive set of styles, components, and design patterns that align with Volto's best practices, you need to set VLT as your theme provider.
-
-Open the {file}`volto.config.js` file in your {file}`frontend` folder, and modify it as shown below.
-
-```{code-block} js
-:emphasize-lines: 2
-
-const addons = ['volto-project-title'];
-const theme = '@kitconcept/volto-light-theme';
-
-module.exports = {
- addons,
- theme
-};
-```
-
-You'll need to restart your Plone frontend to see the changes.
-
-That's it! Your project should now be using Volto Light Theme with its additional blocks and components.
diff --git a/docs/customizing-volto-light-theme/integrate-new-block.md b/docs/customizing-volto-light-theme/integrate-new-block.md
deleted file mode 100644
index 7086e08b4..000000000
--- a/docs/customizing-volto-light-theme/integrate-new-block.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-myst:
- html_meta:
- "description": "Adding New Block"
- "property=og:description": "Adding New Block"
- "property=og:title": "Adding New Block"
- "keywords": "Plone, Volto, Training, Volto Light Theme, Integrate, block"
----
-
-# Integrate a new block
-
-In this training module, we'll learn how to integrate the `@plone-collective/volto-relateditems-block` into VLT. This block allows you to create links to related content in your Plone site.
-
-## Install `@plone-collective/volto-relateditems-block`
-
-To install the related items block, make sure you are in the {file}`frontend/packages/volto-my-project` folder, and use the following command:
-
-```{note}
-For now we'll be using an `alpha` release, so we need to specify the correct version.
-```
-
-```shell
-pnpm install @plone-collective/volto-relateditems-block@1.0.0-alpha.1
-```
-
-After installation, ensure that the add-on is included in the `addons` key of your project's {file}`package.json`:
-
-```json
-"addons": [
- "@eeacms/volto-accordion-block",
- "@kitconcept/volto-button-block",
- "@kitconcept/volto-heading-block",
- "@kitconcept/volto-highlight-block",
- "@kitconcept/volto-introduction-block",
- "@kitconcept/volto-separator-block",
- "@kitconcept/volto-slider-block",
- "@plone-collective/volto-relateditems-block",
- "@kitconcept/volto-light-theme"
-],
-```
-
-You'll need to restart your Plone frontend to see the changes.
-
-That's it! Your project should now be using `@plone-collective/volto-relateditems-block`, which shows related items for the content as a list of links.
diff --git a/docs/customizing-volto-light-theme/question-answer.md b/docs/customizing-volto-light-theme/question-answer.md
index 1c711fda9..4297e39c2 100644
--- a/docs/customizing-volto-light-theme/question-answer.md
+++ b/docs/customizing-volto-light-theme/question-answer.md
@@ -7,4 +7,99 @@ myst:
"keywords": "Plone, Volto, Training, Volto Light Theme"
---
-# Questions and answers
+# Questions and Answers
+
+This chapter collects the questions that come up most often while working through the training.
+Each answer is short, and links to the section that explains it in full.
+
+## Setup
+
+### My styles do not appear at all
+
+Check three things, in order:
+
+1. VLT is declared as the `theme` in your add-on's {file}`package.json`, not only listed in `addons`. It needs both entries.
+2. Your stylesheet is {file}`src/theme/_main.scss`, or is imported from it. Volto only picks up that exact filename.
+3. You restarted the frontend after adding the file. Hot reload does not pick up new files.
+
+See {ref}`light-theme-insertion-points-label`.
+
+### Where do I override `$spacing-large` or a breakpoint?
+
+In {file}`src/theme/_variables.scss`.
+Assigning SCSS variables in {file}`_main.scss` has no effect, because VLT has already used their values by then.
+See {ref}`light-theme-custom-properties-label`.
+
+## Styling
+
+### Should I use `--theme-color` or `--primary-color`?
+
+`--theme-color` and its companions are set per block by the selected block theme, and change as the editor picks a different one. The `--primary-color` family is site-wide. Inside a block, read the theme properties so the block adapts to whichever palette it is given; outside blocks, read the site-wide ones.
+
+### My links are the same color as the surrounding text
+
+That is the default: links use the foreground color of the block theme they sit in.
+Set `--link-foreground-color` in your project to give them a color of their own.
+
+### An editor changed a color in the Theming tab, but the site ignores it
+
+Your stylesheet probably sets the same property.
+See {ref}`light-theme-behavior-colors-label` for how to decide who owns each color.
+
+### My gradient palette shows an empty swatch, or unreadable buttons
+
+Some VLT styles read `--theme-color` with `background-color` or `color`, which ignore a gradient.
+See {ref}`the warning about gradient palettes `.
+
+### Where do the `next--is--...` classes in VLT's CSS come from?
+
+From functions that VLT adds to `config.settings.styleClassNameExtenders`.
+They describe each block's relationship to its neighbors, so that vertical spacing can be expressed in CSS instead of in the components.
+See {ref}`light-theme-generated-classes-label` for the full list.
+
+## Blocks and Widgets
+
+### My alignment or width buttons appear but do nothing
+
+Check two things in the field definition:
+
+- The field name must end in `:noprefix`, as in `align:noprefix` and `blockWidth:noprefix`. Only those names match the style definitions that inject `--block-alignment` and `--block-width`.
+- The `actions` must be the token names from `config.blocks`. Otherwise the widget stores a style object instead of a token.
+
+See {ref}`light-theme-style-fields-label` and {ref}`Step 6 of the Cover block `.
+
+### Can I reuse a Volto block id for my own block?
+
+Avoid it.
+Registering under an existing key replaces that block's configuration instead of adding a new block, and the result then depends on the order in which the add-ons are applied.
+Give project blocks their own key.
+
+### How do I add the background color control to a third-party block?
+
+Compose VLT's `defaultStylingSchema` with the block's existing `schemaEnhancer`.
+See {ref}`the tip at the end of Step 7 of the Cover block `.
+
+## Components
+
+### Should I shadow VLT's header, or swap it?
+
+Swap it.
+VLT 8 resolves its structural components through the registry, so registering your own and naming it in `config.settings.vlt.components` replaces it without shadowing.
+Shadowing still works, but it binds you to an internal module path and hides which component is actually active.
+See {ref}`light-theme-swap-components-label`.
+
+### Slot or component swap?
+
+Use a slot to add something to the layout. Swap a structural component to replace one. Slots are additive and several components can occupy one; a swap substitutes a single named role.
+
+## Block Model v3
+
+### Should I enable it?
+
+Only once every block that your site uses supports it.
+See {ref}`light-theme-block-model-v3-label`.
+
+### Do I need to set categories on VLT's blocks?
+
+No. VLT already assigns them to the blocks it has migrated.
+For your own blocks, reuse a category only when its rules fit the block, or add a category of your own, as explained in {ref}`light-theme-block-categories-label`.
diff --git a/docs/customizing-volto-light-theme/theming.md b/docs/customizing-volto-light-theme/theming.md
deleted file mode 100644
index 5e3d94699..000000000
--- a/docs/customizing-volto-light-theme/theming.md
+++ /dev/null
@@ -1,342 +0,0 @@
----
-myst:
- html_meta:
- "description": "Extend the Volto Light Theme"
- "property=og:description": "Extend the Volto Light Theme"
- "property=og:title": "Extend the Volto Light Theme"
- "keywords": "Plone, Volto, Training, Extend, Volto Light Theme"
----
-
-# Extend VLT
-
-In this section we'll extend the VLT to create a "dark" aesthetic for our project. The same patterns can be applied for other visual identity cases.
-
-## File structure
-
-Let us start by setting up the recommended file structure. In your project add-on's {file}`src` folder, create a subfolder named {file}`theme`.
-Inside {file}`theme` create two empty files named {file}`_main.scss` and {file}`_variables.scss`. Refer to the following file system diagram:
-
-```console
-src/
-├── components
-├── index.js
-└── theme
- ├── _main.scss
- └── _variables.scss
-```
-
-Remember that if you add new files to your project, it will be necessary to restart your Plone frontend.
-
-### `_variables.scss`
-
-{file}`_variables.scss` is where you can override the base theme SCSS variables.
-
-```scss
-:root {
- --primary-color: black;
- --primary-foreground-color: lemonchiffon;
-
- --secondary-color: darkslategrey;
- --secondary-foreground-color: lemonchiffon;
-
- --accent-color: darkslategrey;
- --accent-foreground-color: lemonchiffon;
-
- --link-color: lightblue;
-}
-```
-
-### `_main.scss`
-
-{file}`_main.scss` is where you should put any custom styles. You can also include other SCSS or CSS files, as follows:
-
-```scss
-@import 'variables';
-```
-
-## Block themes
-
-Now we need to change the available themes for the blocks by adding the following definition inside the `applyConfig` function in our project's {file}`index.js`:
-
-```js
-config.blocks.themes = [
- {
- style: {
- '--theme-color': 'black',
- '--theme-high-contrast-color': 'darkslategrey',
- '--theme-foreground-color': 'lemonchiffon',
- '--theme-low-contrast-foreground-color': 'lightgrey',
- },
- name: 'default',
- label: 'Default',
- },
- {
- style: {
- '--theme-color': 'darkslategrey',
- '--theme-high-contrast-color': 'black',
- '--theme-foreground-color': 'lemonchiffon',
- '--theme-low-contrast-foreground-color': 'lightgrey',
- },
- name: 'green',
- label: 'Green',
- },
-];
-```
-
-## Extend add-on styles
-
-The theme provides the ability to extend or modify existing components. Let's create one more file named {file}`relatedItems.scss` to add specific styles for the add-on, as follows:
-
-```console
-src/
-├── components
-├── index.js
-└── theme
- ├── blocks
- └── _relatedItems.scss
- ├── _main.scss
- └── _variables.scss
-```
-
-In the new {file}`_relatedItems.scss`, let's fix the text color, and add background color to the `.inner-container` element with the variables `--primary-foreground-color` and `--theme-high-contrast-color`. Lastly, let's use `--link-foreground-color` for the related items links:
-
-```scss
-.block.relatedItems {
- .inner-container {
- background: var(--theme-high-contrast-color);
- padding: 3rem;
-
- h2.headline {
- color: var(--primary-foreground-color);
- }
-
- ul.items-list {
- color: var(--primary-foreground-color);
- li a {
- color: var(--link-foreground-color);
- }
- }
- }
-}
-```
-
-And now we need to add {file}`_relatedItems.scss` in {file}`_main_.scss`:
-
-```scss
-@import 'blocks/relatedItems';
-@import 'variables';
-```
-
-## Enhancing a block schema
-
-To be able to use the Block Width widget with our Related Items block, we need to add it to the block schema.
-To do this we'll use a `schemaEnhancer`. A schema enhancer is a function that receives an object with `formData` (the block `data`), the `schema` (the original schema that we want to tweak), and the injected `intl` (to aid with internationalization).
-
-Usually we would want to keep the schema enhancers in individual files per block, after which they can be imported to the {file}`index.js`. For this example we'll leave everything in the same file:
-
-```js
-import { defineMessages } from 'react-intl';
-import { composeSchema } from '@plone/volto/helpers/Extensions';
-import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema';
-import { addStyling } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer';
-
-const messages = defineMessages({
- BlockWidth: {
- id: 'Block Width',
- defaultMessage: 'Block Width',
- },
-});
-
-const applyConfig = (config) => {
- config.settings = {
- ...config.settings,
- isMultilingual: false,
- supportedLanguages: ['en'],
- defaultLanguage: 'en',
- };
-
- config.blocks.themes = [
- {
- style: {
- '--theme-color': 'black',
- '--theme-high-contrast-color': 'darkslategrey',
- '--theme-foreground-color': 'lemonchiffon',
- '--theme-low-contrast-foreground-color': 'lightgrey',
- },
- name: 'default',
- label: 'Default',
- },
- {
- style: {
- '--theme-color': 'darkslategrey',
- '--theme-high-contrast-color': 'black',
- '--theme-foreground-color': 'lemonchiffon',
- '--theme-low-contrast-foreground-color': 'lightgrey',
- },
- name: 'green',
- label: 'Green',
- },
- ];
-
- const relatedItemsEnhancer = ({ formData, schema, intl }) => {
- addStyling({ schema, intl });
-
- schema.properties.styles.schema.fieldsets[0].fields = [
- 'blockWidth:noprefix',
- ...schema.properties.styles.schema.fieldsets[0].fields,
- ];
- schema.properties.styles.schema.properties['blockWidth:noprefix'] = {
- widget: 'blockWidth',
- title: intl.formatMessage(messages.BlockWidth),
- default: 'default',
- filterActions: ['narrow', 'default'],
- };
- return schema;
- };
-
- config.blocks.blocksConfig.relatedItems = {
- ...config.blocks.blocksConfig.relatedItems,
- schemaEnhancer: composeSchema(defaultStylingSchema, relatedItemsEnhancer),
- };
-
- return config;
-};
-
-export default applyConfig;
-```
-
-Finally, let's wire the width classes for our block in the file {file}`_relatedItems.scss`:
-
-```scss
-.block.relatedItems {
- margin-right: auto;
- margin-left: auto;
-
- .inner-container {
- background: var(--theme-high-contrast-color);
- padding: 3rem;
-
- h2.headline {
- color: var(--primary-foreground-color);
- }
-
- ul {
- color: var(--primary-foreground-color);
- li a {
- color: var(--link-foreground-color);
- }
- }
- }
-
- &.has--block-width--narrow {
- max-width: var(--narrow-container-width) !important;
- }
-
- &.has--block-width--default {
- max-width: var(--default-container-width) !important;
- }
-}
-
-.block-editor-relatedItems {
- &.has--block-width--narrow .block .block .block {
- max-width: var(--narrow-container-width) !important;
- }
-
- &.has--block-width--default .block .block .block {
- max-width: var(--default-container-width) !important;
- }
-}
-```
-
-
-## Set themes for one block
-
-To demonstrate this feature, we'll add a custom list of themes just for the Related Items block, which will include one more theme called `Blue`. Add the property `themes` to the `relatedItems` block in the `blocksConfig` object:
-
-```js
-import { defineMessages } from 'react-intl';
-import { composeSchema } from '@plone/volto/helpers/Extensions';
-import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema';
-import { addStyling } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer';
-
-const messages = defineMessages({
- BlockWidth: {
- id: 'Block Width',
- defaultMessage: 'Block Width',
- },
-});
-
-const applyConfig = (config) => {
- config.settings = {
- ...config.settings,
- isMultilingual: false,
- supportedLanguages: ['en'],
- defaultLanguage: 'en',
- };
-
- config.blocks.themes = [
- {
- style: {
- '--theme-color': 'black',
- '--theme-high-contrast-color': 'darkslategrey',
- '--theme-foreground-color': 'lemonchiffon',
- '--theme-low-contrast-foreground-color': 'lightgrey',
- },
- name: 'default',
- label: 'Default',
- },
- {
- style: {
- '--theme-color': 'darkslategrey',
- '--theme-high-contrast-color': 'black',
- '--theme-foreground-color': 'lemonchiffon',
- '--theme-low-contrast-foreground-color': 'lightgrey',
- },
- name: 'green',
- label: 'Green',
- },
- ];
-
- const relatedItemsEnhancer = ({ formData, schema, intl }) => {
- addStyling({ schema, intl });
-
- schema.properties.styles.schema.fieldsets[0].fields = [
- 'blockWidth:noprefix',
- ...schema.properties.styles.schema.fieldsets[0].fields,
- ];
- schema.properties.styles.schema.properties['blockWidth:noprefix'] = {
- widget: 'blockWidth',
- title: intl.formatMessage(messages.BlockWidth),
- default: 'default',
- filterActions: ['narrow', 'default'],
- };
- return schema;
- };
-
- config.blocks.blocksConfig.relatedItems = {
- ...config.blocks.blocksConfig.relatedItems,
- schemaEnhancer: composeSchema(defaultStylingSchema, relatedItemsEnhancer),
- themes: [
- ...config.blocks.themes,
- {
- style: {
- '--theme-color': 'midnightblue',
- '--theme-high-contrast-color': 'black',
- '--theme-foreground-color': 'lemonchiffon',
- '--theme-low-contrast-foreground-color': 'lightgrey',
- },
- name: 'blue',
- label: 'Blue',
- },
- ],
- };
-
- return config;
-};
-
-export default applyConfig;
-```
-
-## Conclusion
-
-Understanding how to extend VLT will help you take advantage of the system and quickly create consistent and flexible designs.
diff --git a/docs/mastering-plone/events.md b/docs/mastering-plone/events.md
index 3d4fe99bd..7d40ed9df 100644
--- a/docs/mastering-plone/events.md
+++ b/docs/mastering-plone/events.md
@@ -218,7 +218,7 @@ This trick does not yet work in Volto because some CSS classes are still missing
Modify {file}`frontend/theme/extras/custom.overrides` and add:
-```less
+```css
/* Hide date fields from contributors */
body.userrole-contributor {
#default-start.field,
diff --git a/styles/config/vocabularies/Plone/accept.txt b/styles/config/vocabularies/Plone/accept.txt
index 52aeee0e9..3b8bae882 100644
--- a/styles/config/vocabularies/Plone/accept.txt
+++ b/styles/config/vocabularies/Plone/accept.txt
@@ -17,6 +17,7 @@ Brehault
buildout
bundler
Casali
+Chakra
CMFCore
configlet
Cookiecutter
@@ -117,6 +118,7 @@ pdb
pdbpp
PDF
Pellegrini
+Petch
PhantomJS
Philipp
piwik
@@ -165,6 +167,7 @@ REST
reusability
reutilization
RichText
+Robotarium
Rohberg
Rossum
Runyan
@@ -181,6 +184,8 @@ Steffen
subclassing
subcomponent
subfolders
+subsite
+subsites
subtemplates
sudo
superset
@@ -220,6 +225,7 @@ viewlet
Virtualbox
virtualenv
VMWare
+VLT
Volto
von
VSCode