Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91cb7b8
Retune the slate palette to Craft 5’s grays
brandonkelly Nov 12, 2026
ceb3849
Move the blue palette to Craft 5’s 221° hue
brandonkelly Nov 12, 2026
d7eca67
Give the page and details pane their own surfaces
brandonkelly Nov 12, 2026
ba0b33e
Give the details pane a subtle inset edge shadow
brandonkelly Nov 12, 2026
458400f
Match dashboard widgets to the details surface
brandonkelly Nov 12, 2026
adee8d8
Make Ask AI’s input transparent, like the comment box
brandonkelly Nov 12, 2026
10478cc
Lighten breadcrumb separators
brandonkelly Nov 12, 2026
97bb549
Even out folder breadcrumbs’ separator spacing
brandonkelly Nov 12, 2026
3898662
Keep nav icons in place as the sidebar collapses
brandonkelly Nov 12, 2026
23decd9
Keep a nav item hovered while its flyout is open
brandonkelly Nov 12, 2026
6da0e2d
Keep menu buttons hovered while their menus are open
brandonkelly Nov 12, 2026
22fb5ec
Blend the global header into the page
brandonkelly Nov 12, 2026
454f1ef
Tighten icon button rows in the header and cards
brandonkelly Nov 12, 2026
e357e7e
Size and color the icons around the content alike
brandonkelly Nov 12, 2026
8e14a3e
Changelog for #20612
brandonkelly Nov 12, 2026
5d85d60
Keep cover cards' action buttons spaced apart
brandonkelly Nov 12, 2026
6fdb6f4
Match the mobile header's avatar spacing to its icons'
brandonkelly Nov 12, 2026
7cf4ec3
Theme tooltip contents dark, like a black card's
brandonkelly Nov 12, 2026
f88cac7
Merge remote-tracking branch 'origin/6.x' into ui-tweaks
brandonkelly Nov 12, 2026
c6e26bf
Merge remote-tracking branch 'origin/6.x' into ui-tweaks
brandonkelly Nov 12, 2026
a2c5af7
Put field action menus beside their labels, evenly spaced
brandonkelly Nov 12, 2026
c5577b8
Merge remote-tracking branch 'origin/6.x' into ui-tweaks
brandonkelly Nov 12, 2026
b79d32e
Show fields’ translation indicator as an info icon
brandonkelly Nov 12, 2026
e20f4d8
Drop a breadcrumb trail that’s only the current page
brandonkelly Nov 12, 2026
56d3035
Changelog entries for the translation indicator and lone breadcrumbs
brandonkelly Nov 12, 2026
7abfb6a
Merge remote-tracking branch 'origin/6.x' into ui-tweaks
brandonkelly Nov 12, 2026
647f521
Cover a slotted tooltip invoker's focus and Escape in the tooltip's o…
brandonkelly Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
- Craft now supports Laravel Octane, on Swoole and RoadRunner. Requests served by the same worker no longer share state such as live preview drafts, control panel assets, translated element type names, view namespaces, or routes from the project config, and project config changes made by other workers or commands are picked up by the next request. ([#20641](https://github.com/craftcms/cms/pull/20641))
- Editable tables with a heading column now name each row’s inputs and Reorder and Delete buttons for screen readers after the row’s heading, such as “Opening Time, Wednesday”, rather than its row number. ([#20631](https://github.com/craftcms/cms/pull/20631))
- Admin tables’ delete buttons are now named after their row for screen readers, such as “Delete Posts”, rather than “Delete item”, and deletion confirmations now name the row or count the selected rows, rather than asking “Are you sure?”. ([#20633](https://github.com/craftcms/cms/pull/20633))
- The control panel’s grays and accent blue now match Craft 5’s, and the page background, the details pane, and dashboard widgets each have their own surface. ([#20612](https://github.com/craftcms/cms/pull/20612))
- The global header now blends into the page and lines up with the global sidebar and details rail. Long system names are truncated, and the system name stays visible when the sidebar is collapsed. ([#20612](https://github.com/craftcms/cms/pull/20612))
- The main content’s side padding now grows with its width, from 16px up to 48px, and the content, the details pane, and secondary navigation get matching space at their tops as the page widens, keeping their headers level. Cards’ body padding grows with the card’s own width, from 16px up to 24px, the field layout’s tabs have a larger gap below them, and structure indexes’ toggles and drag handles keep their place beside the content. ([#20599](https://github.com/craftcms/cms/pull/20599))
- Icons in the global sidebar, global header, breadcrumbs, details rail, and content tabs are now the same size and color, and rows of icon buttons in the global header and on element cards are tighter. ([#20612](https://github.com/craftcms/cms/pull/20612))
- Global nav items now stay highlighted while their flyout menus are open, and menu buttons stay highlighted while their menus are open. ([#20612](https://github.com/craftcms/cms/pull/20612))
- The details pane now has a subtle inset shadow along its leading edge, and Ask AI’s input is transparent, like the Activity panel’s comment box. ([#20612](https://github.com/craftcms/cms/pull/20612))
- Fields’ action menus now sit right after their labels and required/translatable indicators, evenly spaced, rather than at the far end of the heading. ([#20612](https://github.com/craftcms/cms/pull/20612))
- Breadcrumb separators are now lighter. ([#20612](https://github.com/craftcms/cms/pull/20612))
- Pages no longer show breadcrumbs when the only one would be the current page, such as the Dashboard. ([#20612](https://github.com/craftcms/cms/pull/20612))
- Fields’ translation indicators now show their descriptions when pressed, including on touch devices, rather than on hover. ([#20612](https://github.com/craftcms/cms/pull/20612))
- Fixed a bug where the user menu showed a “Permissions” item on Craft Solo, which led to a 403 error. ([#20621](https://github.com/craftcms/cms/issues/20621), [#20635](https://github.com/craftcms/cms/pull/20635))
- Fixed a bug where control panel forms could show the wrong fields after a failed save, such as the Base URL field on a new site whose “This site has its own base URL” setting was turned off. ([#20632](https://github.com/craftcms/cms/pull/20632), [#20636](https://github.com/craftcms/cms/pull/20636))
- Fixed missing tab styling, duplicate Save controls, and inconsistent table styling on legacy plugin control panel screens. ([#20591](https://github.com/craftcms/cms/pull/20591))
Expand All @@ -24,6 +34,8 @@
- Fixed a bug where element indexes’ Link columns showed links for disabled, archived, pending, and expired elements. ([#20604](https://github.com/craftcms/cms/issues/20604))
- Fixed a bug where uploaded PNGs could be saved uncompressed when GD was used as the image driver, making them many times larger than the originals. ([#20618](https://github.com/craftcms/cms/pull/20618))
- Fixed a bug where saving the Team user group’s permissions programmatically could save its project config without a name or handle, causing `project-config/apply` to fail. ([#20595](https://github.com/craftcms/cms/issues/20595))
- Fixed a bug where global nav icons shifted when the sidebar was expanded or collapsed. ([#20612](https://github.com/craftcms/cms/pull/20612))
- Fixed a bug where folder breadcrumbs had extra space after each separator. ([#20612](https://github.com/craftcms/cms/pull/20612))
- The `craft:mcp:serve` MCP server now picks up project config changes made by other processes since its previous request. ([#20641](https://github.com/craftcms/cms/pull/20641))
- MCP upload routes now respond with JSON errors, rather than HTML pages, for clients that don’t ask for JSON, such as curl. ([#20578](https://github.com/craftcms/cms/pull/20578))
- The `craftCommand()` Twig function is now only available to control panel templates.
Expand All @@ -35,7 +47,6 @@
- Added `CraftCms\Cms\Asset\AssetTransformers::withImmediateTransforms()`, `immediateTransforms()`, and `IMMEDIATE_TRANSFORMS_CONTEXT`, which apply to every asset transform driver. ([#20588](https://github.com/craftcms/cms/issues/20588), [#20594](https://github.com/craftcms/cms/pull/20594))
- Element details panels registered with `Cp.$elementDetailsPanels.register()` can now pass `iconSvg`, to show their own SVG icon in place of a named one. ([#20615](https://github.com/craftcms/cms/pull/20615))
- Deprecated `CraftCms\Cms\Image\CraftAssetTransformDriver::withImmediateTransforms()` and `IMMEDIATE_TRANSFORMS_CONTEXT`. `CraftCms\Cms\Asset\AssetTransformers::withImmediateTransforms()` and `IMMEDIATE_TRANSFORMS_CONTEXT` should be used instead. ([#20588](https://github.com/craftcms/cms/issues/20588), [#20594](https://github.com/craftcms/cms/pull/20594))
- The main content’s side padding now grows with its width, from 16px up to 48px, and the content, the details pane, and secondary navigation get matching space at their tops as the page widens, keeping their headers level. Cards’ body padding grows with the card’s own width, from 16px up to 24px, the field layout’s tabs have a larger gap below them, and structure indexes’ toggles and drag handles keep their place beside the content. ([#20599](https://github.com/craftcms/cms/pull/20599))

## 6.0.0-beta.1 - 2026-11-10

Expand Down
5 changes: 3 additions & 2 deletions packages/craftcms-ui/scripts/generate-color-palette.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ function printStyles() {
lines.push(staticColors);
lines.push('}\n');
lines.push(
"/* What's in a black card, (filled) chip, or badge is themed dark, so it holds\n" +
' up against the dark fill. (The element itself keeps its own colors.) */'
"/* What's in a black card, (filled) chip, badge, or tooltip is themed dark, so\n" +
' it holds up against the dark fill. (The element itself keeps its own\n' +
' colors.) */'
);
lines.push(`[data-theme='dark'],\n${BLACK_CONTENTS_SELECTOR} {`);
lines.push(darkColors);
Expand Down
4 changes: 2 additions & 2 deletions packages/craftcms-ui/scripts/generate-colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ function generateStyles(paletteColors, semanticColors) {
/*
Also on [data-theme]: these tokens are built from the palette with var(),
which is substituted where it's declared, so a themed subtree re-resolves them.
Likewise what's in a black card, chip, or badge, and a neutral card, so it
doesn't take a tinted card's colors (see utils.js).
Likewise what's in a black card, chip, badge, or tooltip, and a neutral
card, so it doesn't take a tinted card's colors (see utils.js).
*/
:root,
[data-theme],
Expand Down
5 changes: 3 additions & 2 deletions packages/craftcms-ui/scripts/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ export async function resolveFrom(pattern) {
}

/**
* What's in a black card, badge, or (filled) chip, which is themed dark so it
* What's in a black card, badge, (filled) chip, or tooltip, which is themed dark so it
* holds up against the dark fill. Zero specificity, so the contents' own colors
* still win. Matches the copies in tokens.css and variables.css.
*/
export const BLACK_CONTENTS_SELECTOR = `:where(
craft-card[data-color='black'] > *,
craft-chip[data-color='black']:not([appearance~='plain']) > *,
craft-badge[fill='black'] > *
craft-badge[fill='black'] > *,
craft-tooltip > [slot='content'] > *
)`;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export default class CraftBreadcrumbItem extends LitElement {
/* With the crumb's gap, its margin puts as much space before it as the
list's gap puts after it (see craft-breadcrumbs), centering it. */
slot[name='separator']::slotted(*) {
color: var(--c-text-quiet);
color: var(--color-slate-300);
font-size: var(--c-text-base);
font-weight: var(--font-weight-light);
margin-block: 0;
margin-inline: var(--c-spacing-sm) 0;
}
Expand Down
22 changes: 18 additions & 4 deletions packages/craftcms-ui/src/components/button/button.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,10 @@ export default css`

:host([variant~='primary']:hover),
:host([variant~='danger']:hover),
:host([variant~='solid']:hover) {
:host([variant~='solid']:hover),
:host([variant~='primary'][slot='invoker'][aria-expanded='true']),
:host([variant~='danger'][slot='invoker'][aria-expanded='true']),
:host([variant~='solid'][slot='invoker'][aria-expanded='true']) {
background-color: hsl(
from var(--c-color-fill-loud, var(--c-color-neutral-fill-loud)) h s
calc(l - 5)
Expand Down Expand Up @@ -402,7 +405,8 @@ export default css`
color: var(--c-color-on-normal, var(--c-color-neutral-on-normal));
}

:host([variant~='fill']:hover) {
:host([variant~='fill']:hover),
:host([variant~='fill'][slot='invoker'][aria-expanded='true']) {
background-color: hsl(
from var(--c-color-fill-quiet, var(--c-color-neutral-fill-quiet)) h s
calc(l - 5)
Expand Down Expand Up @@ -430,7 +434,16 @@ export default css`
:host([variant~='outline']:not(:disabled):not(.loading):hover),
:host([variant~='dashed']:not(:disabled):not(.loading):hover),
:host([variant~='danger-plain']:not(:disabled):not(.loading):hover),
:host([variant~='plain']:not(:disabled):not(.loading):hover) {
:host([variant~='plain']:not(:disabled):not(.loading):hover),
/* A menu or popover's button keeps its hover state while it's open. */
:host(
:is(
[variant~='outline'],
[variant~='dashed'],
[variant~='danger-plain'],
[variant~='plain']
)[slot='invoker'][aria-expanded='true']
) {
background-color: color-mix(
in oklab,
var(--c-color-fill-quiet, var(--c-color-neutral-fill-quiet)),
Expand Down Expand Up @@ -541,7 +554,8 @@ export default css`
}

:host([variant~='plain'][inherit]:not(:disabled):not(.loading):active),
:host([variant~='plain'][inherit].is-active:not(:disabled):not(.loading)) {
:host([variant~='plain'][inherit].is-active:not(:disabled):not(.loading)),
:host([variant~='plain'][inherit][slot='invoker'][aria-expanded='true']) {
color: inherit;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,37 @@ it('keeps separated fields within a group too narrow for its full column gap', a
const field = fieldGroup.querySelector('div')!;
expect(field.getBoundingClientRect().width).toBe(300);
});

it('drops a required sub-field’s asterisk in a required fieldset, leaving no gap for it', async () => {
await import('../../styles/cp.css');
await import('../field/field.js');
document.body.innerHTML = `
<craft-field fieldset required label="Field Limit">
<craft-field-group slot="input">
<craft-field required label="Minimum"><input slot="input"><button slot="actions">A</button></craft-field>
<craft-field label="Maximum"><input slot="input"><button slot="actions">A</button></craft-field>
</craft-field-group>
</craft-field>
`;
const [outer, required, optional] = [
...document.querySelectorAll('craft-field'),
];
for (const field of [outer!, required!, optional!]) {
await field.updateComplete;
}

const actionsOffset = (field: Element) => {
const label = field.querySelector(':scope > [slot="label"]')!;
const range = document.createRange();
range.selectNodeContents(label.firstChild!);

return (
field.querySelector('button')!.getBoundingClientRect().left -
range.getBoundingClientRect().right
);
};

const asterisk = required!.querySelector('craft-icon[name="asterisk"]')!;
expect(getComputedStyle(asterisk).display).toBe('none');
expect(actionsOffset(required!)).toBeCloseTo(actionsOffset(optional!), 0);
});
16 changes: 15 additions & 1 deletion packages/craftcms-ui/src/components/field-group/field-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,24 @@ export default class CraftFieldGroup extends LitElement {
> craft-field-group
craft-field[required]
> [slot='label']
> craft-icon[name='asterisk'][data-craft-field-decoration] {
craft-icon[name='asterisk'][data-craft-field-decoration] {
display: none;
}

craft-field[fieldset][required]
> craft-field-group
craft-field[required]
> [slot='label']
> [data-craft-field-decoration]:not(:has(craft-info-icon)) {
display: contents !important;
}

craft-field[fieldset][required]
> craft-field-group
craft-field[required]:not([translatable])::part(actions) {
margin-inline-start: var(--_label-text-bearing);
}

@container (min-width: 30rem) {
craft-field-group.auto-widths > :not([class*='width-']) {
grid-column: span 2;
Expand Down
4 changes: 2 additions & 2 deletions packages/craftcms-ui/src/components/field/Field.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Errors render in the `feedback` slot and are wired to the control with `aria-des

<Canvas of={FieldStories.Translatable} sourceState="shown" />

The status badge marks a field whose value is outdated or overridden. The translation indicator marks a field whose value is per-site, with `translation-description` explaining the scope.
The status badge marks a field whose value is outdated or overridden. The translation indicator, an info icon, marks a field whose value is per-site; pressing it shows `translation-description`, explaining the scope.

## Fieldsets

Expand All @@ -69,7 +69,7 @@ Use it when the field holds several controls that share one question — a group

<Canvas of={FieldStories.Actions} sourceState="shown" />

The `actions` slot holds field-level controls: a hide-label toggle, a copy-value button, a settings menu.
The `actions` slot holds field-level controls: a hide-label toggle, a copy-value button, a settings menu. They follow a visible label and its indicators, or sit at the end of the heading when the label is visually hidden.

`label-extra` does something similar and is **deprecated** — use `actions`.

Expand Down
8 changes: 4 additions & 4 deletions packages/craftcms-ui/src/components/field/field.a11y.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The field supplies the label, instructions, notices, and errors around a slotted
- [x] `label-sr-only` hides the label visually but keeps it as the control's accessible name. Per WCAG 4.1.2 (Name, Role, Value). Verified by `leaves no space above the input for a visually hidden label` in `field.browser.test.ts` and `visually hides the whole heading when the label is all it has` in `field.test.ts`.
- [x] `instructions-sr-only` hides the instructions visually but keeps them in the input's accessible description, for a field whose placeholder repeats them, as a placeholder alone isn't reliably announced and disappears once there's text. Per WCAG 1.3.1 (Info and Relationships). Verified by `visually hides the instructions but keeps them in the description` in `field.test.ts`.
- [ ] Field actions are grouped with `role="group"` and the name "Field actions". Per WCAG 1.3.1 (Info and Relationships). Not yet verified. `groups slotted actions` in `field.test.ts` checks the role but not the name.
- [ ] The translation indicator sits inside the label node, so its name may be folded into the control's accessible name, and it puts a button inside a `<label>`. Per WCAG 4.1.2 (Name, Role, Value). Not yet verified.
- [ ] The translation indicator sits inside the label node, so its name may be folded into the control's accessible name, and it puts a button (in `craft-info-icon`'s shadow root) inside a `<label>`. Per WCAG 4.1.2 (Name, Role, Value). Not yet verified.

### Forms and validation

Expand All @@ -37,8 +37,8 @@ The field supplies the label, instructions, notices, and errors around a slotted

### Keyboard and focus

- [x] The translation indicator is a button named by `translation-description`. It's reachable with Tab, opens its tooltip on focus, closes it with Escape while keeping focus, and Tab then moves on to the control. Per WCAG 2.1.1 (Keyboard), 2.4.3 (Focus Order), 4.1.2 (Name, Role, Value) and 1.4.13 (Content on Hover or Focus). Verified by `exposes translation help on keyboard focus and dismisses it with Escape` in `field.browser.test.ts`.
- [x] The translation indicator is at least 24×24 CSS px. Per WCAG 2.5.8 (Target Size (Minimum)). Verified by `exposes translation help on keyboard focus and dismisses it with Escape` in `field.browser.test.ts`.
- [x] The translation indicator is a `craft-info-icon` whose button is named by `translation-description`. It's reachable with Tab, opens its tooltip on Enter, closes it with Escape while keeping focus, and Tab then moves on to the control. Per WCAG 2.1.1 (Keyboard), 2.4.3 (Focus Order) and 4.1.2 (Name, Role, Value). Verified by `explains a translatable field in an info icon, opened and closed from the keyboard` in `field.browser.test.ts`.
- [ ] The translation indicator is at least 24×24 CSS px, or has enough space around it. Per WCAG 2.5.8 (Target Size (Minimum)). Known gap: `craft-info-icon`'s button is only as big as its icon, about 16×13 CSS px, and beside a visible label a flush action button that follows it starts within 12px of its center.

### Visual

Expand All @@ -48,7 +48,7 @@ The field supplies the label, instructions, notices, and errors around a slotted

### Reflow and zoom

- [ ] At a 320px viewport and at 200% zoom, the heading wraps and the control shrinks to fit, with nothing clipped or overlapping. Per WCAG 1.4.10 (Reflow) and 1.4.4 (Resize Text). Not yet verified. The actions group is absolutely positioned at the end of the heading, so a long label may run under it.
- [ ] At a 320px viewport and at 200% zoom, the heading wraps and the control shrinks to fit, with nothing clipped or overlapping. Per WCAG 1.4.10 (Reflow) and 1.4.4 (Resize Text). Not yet verified. With the label visually hidden, the actions group is absolutely positioned at the end of the heading, so other heading content may run under it.

## Depends on usage

Expand Down
Loading
Loading