Repository navigation
feat: native flexbox layout controls for group/column/columns/grid-item #596
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
ViewFromTheBox
merged 5 commits into
release/1.2
from
feature/595-native-flex-layout-controls
Jun 16, 2026
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
9f83260
feat: native flexbox layout controls for group/column/columns/grid-it…
ViewFromTheBox 1d8a07f
chore: address CodeRabbit findings (#595)
ViewFromTheBox 9f95c60
chore: round-two CodeRabbit fixes (#595)
ViewFromTheBox b0ef484
chore: round-three CodeRabbit fixes (#595)
ViewFromTheBox 1554797
chore: switch escapeSelector to denylist (#595)
ViewFromTheBox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| # Flex Layout (#595) | ||
|
|
||
| The Flex Layout inspector panel exposes the full CSS flexbox property | ||
| surface on `artisanpack/group`, `artisanpack/columns`, | ||
| `artisanpack/column`, and `artisanpack/grid-item`. It is per-breakpoint | ||
| via the existing `<ViewportSwitcher />` so every value cascades the | ||
| same way `spacing` / `width` do. | ||
|
|
||
| ## Where it appears | ||
|
|
||
| A "Flex Layout" panel renders in the block inspector for all four | ||
| blocks above. On `artisanpack/group` it **replaces** WordPress core's | ||
| Flex layout variation (the core flex choice is hidden via a | ||
| `blocks.registerBlockType` filter). On `artisanpack/columns` it | ||
| **layers on** core's column-distribution: our values win only when | ||
| explicitly set. | ||
|
|
||
| A sibling "Flex Item" panel appears on `group`, `column`, and | ||
| `grid-item` whenever the block is the direct child of a flex | ||
| container. When the parent is not flex, the panel renders disabled | ||
| with a tooltip — the values are not silently deleted. | ||
|
|
||
| ## Container controls | ||
|
|
||
| | Control | CSS property | Values | | ||
| | --- | --- | --- | | ||
| | **Enable Flex** | `display: flex` | toggle | | ||
| | **Direction** | `flex-direction` | `row`, `column`, `row-reverse` (advanced), `column-reverse` (advanced) | | ||
| | **Wrap** | `flex-wrap` | `nowrap`, `wrap`, `wrap-reverse` (advanced) | | ||
| | **Justify Content** | `justify-content` | `flex-start`, `center`, `flex-end`, `space-between`; advanced: `space-around`, `space-evenly`, `start`, `end`, `left`, `right` | | ||
| | **Align Items** | `align-items` | `stretch`, `flex-start`, `center`, `flex-end`, `baseline`; advanced: `start`, `end`, `self-start`, `self-end`, `first baseline`, `last baseline` | | ||
| | **Align Content** | `align-content` | `stretch`, `flex-start`, `center`, `flex-end`, `space-between`; advanced: `space-around`, `space-evenly`, `start`, `end`, `baseline`. Disabled unless Wrap is `wrap` / `wrap-reverse`. | | ||
| | **Place Content** | `place-content` (shorthand) | advanced only; greys per-axis controls when set, with a "Reset shorthand" link. | | ||
| | **Row Gap** | `row-gap` | any CSS length | | ||
| | **Column Gap** | `column-gap` | any CSS length | | ||
|
|
||
| ## Item controls | ||
|
|
||
| | Control | CSS property | Values | | ||
| | --- | --- | --- | | ||
| | **Align Self** | `align-self` | `auto`, `flex-start`, `center`, `flex-end`, `stretch`, `baseline`; advanced: `start`, `end`, `self-start`, `self-end` | | ||
| | **Grow** | `flex-grow` | numeric (0–999) | | ||
| | **Shrink** | `flex-shrink` | numeric (0–999) | | ||
| | **Basis** | `flex-basis` | `auto`, `0`, `full`, `fit-content`, `max-content`, `min-content`, or any CSS length | | ||
| | **Order** | `order` | numeric (-999 to 999) | | ||
|
|
||
| ## Generated classes | ||
|
|
||
| The serializer emits utility classes on the wrapper (no inline styles, | ||
| no CSS custom properties). Shape mirrors Tailwind: | ||
|
|
||
| ``` | ||
| ap-flex | ||
| ap-flex-{row|col|row-reverse|col-reverse} | ||
| ap-flex-{nowrap|wrap|wrap-reverse} | ||
| ap-justify-{start|center|end|between|around|evenly|left|right|stretch|baseline} | ||
| ap-items-{start|center|end|stretch|baseline|self-start|self-end|first-baseline|last-baseline} | ||
| ap-content-{start|center|end|between|around|evenly|stretch|baseline} | ||
| ap-place-content-[value] | ||
| ap-gap-x-[value] ap-gap-y-[value] | ||
|
|
||
| ap-self-{auto|start|center|end|stretch|baseline|self-start|self-end} | ||
| ap-grow-{n} ap-grow-[n] | ||
| ap-shrink-{n} ap-shrink-[n] | ||
| ap-basis-{auto|0|full|...} ap-basis-[value] | ||
| ap-order-{n} ap-order-[n] | ||
| ``` | ||
|
|
||
| Responsive prefixes: `sm:`, `md:`, `lg:`, `xl:`, `2xl:` — matching the | ||
| keys registered in `BreakpointRegistry`. | ||
|
|
||
| Arbitrary values (e.g. `ap-gap-x-[3.5rem]`, `ap-basis-[200px]`) come | ||
| with a per-page `<style>` snippet emitted by the renderer — they don't | ||
| balloon the static stylesheet. | ||
|
|
||
| ## Theme.json defaults | ||
|
|
||
| Themes can opt out or set defaults via `settings.artisanpack.flex` in | ||
| their theme.json: | ||
|
|
||
| ```php | ||
| 'settings' => [ | ||
| 'artisanpack' => [ | ||
| 'flex' => [ | ||
| 'enable' => true, | ||
| 'defaultDirection' => 'row', | ||
| 'defaultJustifyContent' => null, | ||
| 'defaultAlignItems' => null, | ||
| 'defaultGap' => [ 'row' => null, 'column' => null ], | ||
| ], | ||
| ], | ||
| ], | ||
| ``` | ||
|
|
||
| Setting `enable` to `false` hides both panels in the inspector but | ||
| does **not** change the rendering of already-saved content — wrapper | ||
| classes still emit through the renderer. | ||
|
|
||
| ## Renderer parity | ||
|
|
||
| The single source of truth is `resources/js/visual-editor/blocks/ | ||
| _shared/flex-controls/serializer.ts`. The Blade `FlexSupport`, the | ||
| React renderer's `support/flex-serializer.ts`, and the Vue renderer's | ||
| `support/flex-serializer.ts` all mirror that exactly. Parity is | ||
| guarded by a shared `fixtures.json` consumed by: | ||
|
|
||
| - the editor Jest suite (`serializer.test.ts`) | ||
| - the Blade Pest suite (`FlexSupportTest.php`) | ||
| - the React vitest suite (`flex-serializer.test.ts`) | ||
| - the Vue vitest suite (`flex-serializer.test.ts`) | ||
|
|
||
| Adding a new behavior means adding a fixture; every renderer's CI run | ||
| fails until it produces the expected output. | ||
|
|
||
| ## Migration from `layout.type === 'flex'` | ||
|
|
||
| Group blocks with the legacy `layout.type === 'flex'` shape migrate | ||
| to `artisanpackFlex.container` on first edit via the block | ||
| deprecation chain. Unedited content keeps rendering through its | ||
| existing markup. See [[migration/From Crosswinds Blocks]] for the | ||
| crosswinds-blocks → ArtisanPack UI mapping if you're coming from that | ||
| plugin. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Migrating from `crosswinds-blocks` (#595) | ||
|
|
||
| The legacy `crosswinds-blocks` plugin added a narrow set of flexbox | ||
| helpers to `core/group`, `core/column`, and `core/post-template` — | ||
| three toggles plus a five-option dropdown. The ArtisanPack UI Flex | ||
| Layout panel covers the full flexbox surface and replaces the | ||
| crosswinds helpers entirely. | ||
|
|
||
| **There is no automatic migration**: crosswinds-blocks ran in a | ||
| separate project and we don't expect its content to land in | ||
| visual-editor pages. This page is the hand-migration reference if you | ||
| do bring content over. | ||
|
|
||
| ## Mapping | ||
|
|
||
| | crosswinds attribute | New attribute | Notes | | ||
| | --- | --- | --- | | ||
| | `cbUseFlex: true` | `artisanpackFlex.container.enabled: { base: true }` | Toggle the Enable Flex switch. | | ||
| | `cbFillHeight: true` | `artisanpackFlex.item.grow: { base: 1 }` on the child you want to fill | Crosswinds applied this to the child; ours is a per-item property. | | ||
| | `cbInnerLayout: 'normal'` | (no flex; default flow) | Disable the panel. | | ||
| | `cbInnerLayout: 'equal'` | `direction: column` + `justifyContent: space-between` | Vertical column with children pushed apart. | | ||
| | `cbInnerLayout: 'center'` | `alignItems: center` (+ `justifyContent: center` if you want both axes) | Centers children. | | ||
| | `cbInnerLayout: 'bottom'` | `direction: column` + `justifyContent: flex-end` | All children pushed to the bottom. | | ||
| | `cbInnerLayout: 'last-bottom'` | On the last child: `alignSelf: flex-end` (column direction) or `marginTop: auto` (row direction) | Crosswinds pinned the last item; align-self gives equivalent control. | | ||
|
|
||
| ## Why we didn't port the enum as-is | ||
|
|
||
| The crosswinds enum solved a real problem (authors couldn't reach | ||
| "fill remaining height" / "center children" without dropping into the | ||
| Advanced panel), but it masked a much richer property surface. | ||
| Replacing it with per-property controls means every author can express | ||
| any flexbox layout without writing CSS — the original goal — and the | ||
| panel grows naturally as flexbox itself does. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
packages/visual-editor-renderer-blade/resources/views/blocks/core/column.blade.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.