diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17a9776f..4738f526 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## Unreleased
+### Features
+- **layout:** add `.sf-place-center` — box-neutral centring primitive (`grid` + `place-items:center`) that centres content on both axes without adopting cluster/stack/imposter semantics
+
## [0.7.27] - 2026-07-23
### Breaking Changes
diff --git a/configurator/src/data/classes.generated.json b/configurator/src/data/classes.generated.json
index 6e6ecb76..b7afc728 100644
--- a/configurator/src/data/classes.generated.json
+++ b/configurator/src/data/classes.generated.json
@@ -2304,6 +2304,16 @@
"optional": false,
"layer": "slashed.layout"
},
+ {
+ "name": "sf-place-center",
+ "selector": ".sf-place-center",
+ "kind": "layout",
+ "category": "Layout primitives",
+ "group": "",
+ "description": "Box-neutral centring primitive: display:grid + place-items:center centres an element's content on both axes without adopting cluster/stack/imposter semantics. Unlike sf-center (which centres itself), this centres the content; give the box a height for block-axis centring. The named form of the display:grid; place-items:center idiom.",
+ "optional": false,
+ "layer": "slashed.layout"
+ },
{
"name": "sf-prose",
"selector": ".sf-prose",
diff --git a/core/layout.css b/core/layout.css
index ebedefb9..ceafed57 100644
--- a/core/layout.css
+++ b/core/layout.css
@@ -246,7 +246,19 @@
align-items: center;
}
-
+ /* Place-center — the box-neutral centring primitive. Unlike .sf-center (which
+ centres ITSELF as a max-width wrapper) this centres an element's CONTENT on
+ both axes, and unlike the flow primitives' --center modifiers it imposes no
+ row/column/wrap semantics: a plain element becomes a centring box. It is
+ the framework's named form of the community `display:grid; place-items:
+ center` idiom (cf. Tailwind's place-items-center). Give the box a size
+ (min-block-size / height) for the block-axis centring to have room. */
+ .sf-place-center {
+ display: grid;
+ place-items: center;
+ }
+
+
/* Cluster */
.sf-cluster {
display: flex;
diff --git a/demo/index.html b/demo/index.html
index c03fcbf7..b0b22d3c 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -3166,6 +3166,10 @@
Layout primitives .sf-center--intrinsicpublic
Modifier for sf-center: constrains the container to the natural width of its children (no stretching to fill available space).
centered & width-limited
+
+ .sf-place-centerpublic
Box-neutral centring primitive: display:grid + place-items:center centres an element's content on both axes, without adopting cluster/stack/imposter semantics. Give the box a height for block-axis centring.
+
centred both axes
+ .sf-clusterpublic
Flex-wrap row of variable-width items with automatic gap. Children wrap when the container is too narrow. Use for tag lists, button groups, and icon rows.
OneTwoThreeFourFive
@@ -5077,7 +5081,7 @@
Design tokens 7
--sf-z-tooltip
-
+
diff --git a/docs/api-index.json b/docs/api-index.json
index 47fa5d25..94e1c870 100644
--- a/docs/api-index.json
+++ b/docs/api-index.json
@@ -25,14 +25,14 @@
"optimal"
],
"counts": {
- "total": 1068,
+ "total": 1069,
"by_type": {
- "class": 327,
+ "class": 328,
"token": 741
},
"by_tier": {
"INTERNAL": 1,
- "PUBLIC": 1018,
+ "PUBLIC": 1019,
"PUBLIC-ADVANCED": 49
},
"by_role": {
@@ -40,8 +40,8 @@
"knob": 253
},
"tokens": 741,
- "classes": 327,
- "sf_classes": 304,
+ "classes": 328,
+ "sf_classes": 305,
"is_classes": 15,
"unprefixed_classes": 8,
"by_category": {
@@ -50,7 +50,7 @@
"Components": 30,
"Core tokens": 610,
"Forms": 1,
- "Layout primitives": 145,
+ "Layout primitives": 146,
"Layout tokens": 53,
"Macro classes": 51,
"Macro tokens": 32,
@@ -24555,6 +24555,29 @@
"optimal"
]
},
+ {
+ "name": "sf-place-center",
+ "type": "class",
+ "tier": "PUBLIC",
+ "selector": ".sf-place-center",
+ "prefix": "sf",
+ "kind": "layout",
+ "category": "Layout primitives",
+ "area": "layout",
+ "group": "",
+ "description": "Box-neutral centring primitive: display:grid + place-items:center centres an element's content on both axes without adopting cluster/stack/imposter semantics. Unlike sf-center (which centres itself), this centres the content; give the box a height for block-axis centring. The named form of the display:grid; place-items:center idiom.",
+ "isVariant": false,
+ "baseClass": null,
+ "optional": false,
+ "layer": "slashed.layout",
+ "sourceFiles": [
+ "core/layout.css"
+ ],
+ "bundles": [
+ "full",
+ "optimal"
+ ]
+ },
{
"name": "sf-prose",
"type": "class",
diff --git a/docs/api-index.md b/docs/api-index.md
index 2f2f3f2f..ea0acb94 100644
--- a/docs/api-index.md
+++ b/docs/api-index.md
@@ -10,11 +10,11 @@ and a short description. The machine-readable companion (with all columns) is
[registry.json](registry.json); for the tier contract see
[architecture.md](architecture.md).
-**1068 elements** — 741 tokens, 327 classes.
+**1069 elements** — 741 tokens, 328 classes.
| Tier | Count | Meaning |
|---|---|---|
-| PUBLIC | 1018 | Everyday surface. SemVer-stable. |
+| PUBLIC | 1019 | Everyday surface. SemVer-stable. |
| PUBLIC-ADVANCED | 49 | Same SemVer guarantee; niche/powerful. |
| INTERNAL | 1 | Implementation detail; may change without a major bump. |
@@ -781,7 +781,7 @@ and a short description. The machine-readable companion (with all columns) is
| `--sf-surface-bg-size` | PUBLIC | knob | surface | `cover` | background-size for the .sf-surface-bg image. Default: cover. |
| `--sf-surface-color` | PUBLIC | consumption | surface | `var(--sf-color-base)` | Input for the generic .sf-surface macro. Set any color (including palette shades); the macro derives background, auto-contrast foreground, and the contextual token set from it. |
-## Classes (327)
+## Classes (328)
### Accessibility (9)
@@ -838,7 +838,7 @@ and a short description. The machine-readable companion (with all columns) is
|---|---|---|---|---|
| `.sf-live-validate` | PUBLIC | form | — | Scopes native constraint-validation feedback to this subtree: within it, :user-invalid/:user-valid on input/select/textarea drive --sf-field-border-color (the same token .sf-is-invalid/.sf-is-valid set explicitly). Apply to a