use-wc 1.0.0-pre.64 → 1.0.0-pre.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1790,45 +1790,19 @@
1790
1790
  }
1791
1791
  }
1792
1792
 
1793
- /* Card — `use-card` is a theme-only tag, styled like `use-field`/`use-selector` with no
1794
- registration behind it: an author can still add `role`/ARIA directly on `<use-card>` for real
1795
- semantics, the same as any other element, just not for free the way `<article>`/`<section>`
1796
- give an implicit landmark. It supplies the surface chrome (background/border/radius/shadow);
1797
- `shell.css` (declared later, so it wins on `display`) makes it a flex column, and
1798
- `surface.css` styles its header/footer/figure regions. `.outlined` drops the shadow and keeps
1799
- the border, for a flatter, less "floating" card. */
1800
- @layer use-theme.card {
1801
- use-card {
1802
- display: block;
1803
- overflow: hidden;
1804
- background: var(--usewc-color-surface-background);
1805
- border: var(--usewc-layout-surface-border) solid var(--usewc-color-surface-border);
1806
- border-radius: var(--usewc-effect-surface-border-radius);
1807
- box-shadow: var(--usewc-effect-surface-shadow);
1808
- color: var(--usewc-color-document-text);
1809
-
1810
- use-theme-escape & {
1811
- all: revert-layer;
1812
- }
1813
- }
1814
-
1815
- use-card.outlined {
1816
- box-shadow: none;
1817
-
1818
- use-theme-escape & {
1819
- all: revert-layer;
1820
- }
1821
- }
1822
- }
1823
-
1824
1793
  /* Dialog — a native `<dialog>` already provides the overlay mechanics (top-layer promotion,
1825
1794
  `::backdrop`, focus trapping, Esc-to-close) for free once shown with `.showModal()`. Themed by
1826
1795
  default, like the rest of this family's native elements — `:not(.appearance-native)` is the
1827
1796
  escape hatch, not an opt-in class. The default look centers (the UA default for `<dialog>`, so
1828
1797
  no extra positioning is needed) and constrains its size; `.drawer` is the one variant that
1829
1798
  needs an opt-in class, since it pins to a block-size-100dvh edge panel instead — the positioning
1830
- itself, not just the chrome, changes. That's what makes `use-shell-fill` (see `shell.css`)
1831
- matter for a drawer specifically — the body scrolls while header/footer stay put. */
1799
+ itself, not just the chrome, changes.
1800
+
1801
+ Chrome only — a `<dialog>` gets no layout from this family. Nest a `use-layout` variant
1802
+ inside it (`layout.css`) to arrange a header / body / footer, which leaves the element's
1803
+ UA-controlled `display` alone and lets one dialog host any variant. That nested layout is
1804
+ what makes a drawer work: its `use-layout[fill]` body scrolls while header and footer stay
1805
+ put over the full 100dvh. */
1832
1806
  @layer use-theme.dialog {
1833
1807
  dialog:not(.appearance-native) {
1834
1808
  padding: 0;
@@ -1838,8 +1812,8 @@
1838
1812
  background: var(--usewc-color-surface-background);
1839
1813
  box-shadow: var(--usewc-effect-surface-shadow);
1840
1814
  color: var(--usewc-color-document-text);
1841
- inline-size: min(32rem, 100% - var(--usewc-space-surface-padding) * 2);
1842
- max-block-size: min(80dvh, 100% - var(--usewc-space-surface-padding) * 2);
1815
+ inline-size: min(32rem, 100% - var(--usewc-layout-surface-padding) * 2);
1816
+ max-block-size: min(80dvh, 100% - var(--usewc-layout-surface-padding) * 2);
1843
1817
 
1844
1818
  use-theme-escape & {
1845
1819
  all: revert-layer;
@@ -1886,7 +1860,8 @@
1886
1860
  is already a specific enough signal on its own). Anchor positioning (`anchor-name`/
1887
1861
  `position-anchor`) is left to the author, the same way plain `[popover]` elements work
1888
1862
  otherwise — this only replaces the UA default chrome with the shared `surface` tokens,
1889
- matching `dialog`/`use-card`.
1863
+ matching `dialog` / `use-layout.card`. Chrome only, like `dialog`: nest a `use-layout`
1864
+ variant inside to arrange a header / body / footer (`layout.css`).
1890
1865
 
1891
1866
  The `::backdrop` is deliberately left at its transparent UA default: a `[popover]` at
1892
1867
  `popover="auto"` is non-modal — the page stays interactive and an outside click dismisses it
@@ -1907,8 +1882,46 @@
1907
1882
  background: var(--usewc-color-surface-background);
1908
1883
  box-shadow: var(--usewc-effect-surface-shadow);
1909
1884
  color: var(--usewc-color-document-text);
1910
- max-inline-size: min(24rem, 100% - var(--usewc-space-surface-padding) * 2);
1911
- max-block-size: min(80dvh, 100% - var(--usewc-space-surface-padding) * 2);
1885
+ max-inline-size: min(24rem, 100% - var(--usewc-layout-surface-padding) * 2);
1886
+ max-block-size: min(80dvh, 100% - var(--usewc-layout-surface-padding) * 2);
1887
+
1888
+ use-theme-escape & {
1889
+ all: revert-layer;
1890
+ }
1891
+ }
1892
+ }
1893
+
1894
+ /* Anchored surface positioning — a modal `<dialog>` or `[popover]` positioned against an
1895
+ invoker (wrapped in `use-anchored`) keeps a small gap from it and stays willing to flip to
1896
+ the other side on scroll. An anchored surface carries no region layout of its own — nest a
1897
+ `use-layout` variant inside it, the same as any other `dialog`/`[popover]` (`layout.css`). */
1898
+ @layer use-theme.anchored {
1899
+ /* An anchored surface keeps a small gap from its anchor, the same convention `use-menu`
1900
+ follows in `menu.css`. Scoped to `use-anchored`'s direct child — the one reliable "this is
1901
+ anchored" signal — so a standalone `[popover]` or modal `<dialog>` elsewhere keeps its UA
1902
+ centering. `margin-block` (both edges) so the gap survives a `position-try` flip to the
1903
+ other side. The open-state check mirrors the positioning util: `:popover-open` for a
1904
+ popover, `[open]` for a `<dialog>`.
1905
+
1906
+ The `max-block-size` override is what lets it flip on scroll instead of shrinking.
1907
+ `popover.css` / `dialog.css` cap the surface at `min(80dvh, 100% - <pad>)`, where `100%`
1908
+ is the free space on the anchor's *current* side — so as the anchor nears a viewport edge
1909
+ that cap shrinks with it, the surface never overflows, and `position-try` never sees the
1910
+ overflow it needs to flip (or to flip back). Reworking the cap to
1911
+ `min(80dvh, max(45dvh, 100% - <pad>))` keeps the surface willing to be at least 45dvh: a
1912
+ side smaller than that overflows and triggers a flip, and since sibling sides sum to nearly
1913
+ the full viewport a 45dvh surface always fits the other one, so the flip lands on-screen.
1914
+ Where a side has more than 45dvh the `100%` term still tracks it, up to 80dvh. Left at the
1915
+ default `position-try-order` (not `most-block-size`) so the surface stays on the block-end
1916
+ (below) side whenever the content fits there, only flipping above when it doesn't — rather
1917
+ than jumping to whichever side merely has more room. `overflow` (otherwise `hidden`)
1918
+ scrolls the content once the cap bites; a surface with a `[fill]` region already scrolls
1919
+ that region (`layout.css`), so the surface itself never overflows there and this `overflow`
1920
+ is inert. */
1921
+ use-anchored > :is(dialog, [popover]):not(use-menu, .appearance-native):is([open], :popover-open) {
1922
+ margin-block: var(--usewc-layout-surface-anchor-offset);
1923
+ max-block-size: min(80dvh, max(45dvh, 100% - var(--usewc-layout-surface-padding) * 2));
1924
+ overflow: auto;
1912
1925
 
1913
1926
  use-theme-escape & {
1914
1927
  all: revert-layer;
@@ -1916,86 +1929,351 @@
1916
1929
  }
1917
1930
  }
1918
1931
 
1919
- /* Shell — `use-shell` is a theme-only tag (no registration, styled by tag name like
1920
- `use-field`/`use-selector`/`use-card`) providing a generic flex-column-with-one-growing-region
1921
- mechanic, usable anywhere in an app that wants it — not specific to card/dialog/popover
1922
- surfaces. Every direct child defaults to `flex: 0 0 auto` (fixed, doesn't grow or shrink) and
1923
- `min-block-size: 0`; `use-shell-fill` is the one region that grows to fill the remaining
1924
- space and scrolls its own overflow, wherever it appears.
1925
-
1926
- The three surface hosts (`dialog`, `use-card`, `[popover]`) participate in the same mechanic
1927
- directly — no `use-shell` wrapper needed inside them — since they're already the flex
1928
- container an author nests header/use-shell-fill/footer straight into. This layer is declared
1929
- after `card.css`/`dialog.css`/`popover.css`, so its `display: flex` wins over their own
1930
- `display: block` for `use-card` (dialog/popover don't set `display` themselves, so there's
1931
- nothing to override there).
1932
-
1933
- `dialog` and `[popover]` are scoped to `:modal`/`:popover-open` here, unlike the chrome rules
1934
- in `dialog.css`/`popover.css` — a closed `<dialog>`/`[popover]` is `display: none` by the UA
1935
- stylesheet, and normal-importance author CSS always beats normal-importance UA CSS regardless
1936
- of layers, so an unconditional `display: flex` here would un-hide a closed one. Chrome
1937
- properties (background/border/shadow/sizing) don't have this problem — they're inert on a
1938
- `display: none` element either way — only `display` itself does. */
1939
- @layer use-theme.shell {
1940
- use-shell,
1941
- dialog:modal:not(.appearance-native),
1942
- use-card,
1943
- [popover]:popover-open:not(use-menu, .appearance-native) {
1932
+ /* Flex layout — `use-layout` is a theme-only tag (no custom element registration, styled by
1933
+ tag name like `use-field`/`use-selector`), providing a configurable flex container:
1934
+ direction, alignment, justification, a named gap scale, wrapping, and inline vs block
1935
+ display, all driven by attributes. It defaults to a column, since stacking is the common
1936
+ case for the page/panel structure it's used for; set `direction="row"` for a row.
1937
+
1938
+ When a direct child `use-layout` carries `fill`, that child grows to consume the remaining
1939
+ space and the other children are pinned so they can't shrink; with no such child,
1940
+ `use-layout` is a bare flex container that leaves its children alone. `fill` is read *only*
1941
+ on a `use-layout`, and only as a direct child, so an `<svg fill="…">` or an author's own
1942
+ `fill` attribute anywhere in the tree is untouched — no exclusion list needed.
1943
+
1944
+ Four variant classes name what the container *is*, and its semantic `<figure>` / `<header>` /
1945
+ `<main>` / `<footer>` regions are arranged to match:
1946
+
1947
+ use-layout.page an app frame — ruled topbar and footer, roomy
1948
+ use-layout.prose a document — stacked title/byline header, measure-capped, loose
1949
+ use-layout.message a chat or comment — an avatar rail with the regions beside it, compact
1950
+ use-layout.card a panel — avatar + hgroup + actions, end-aligned ruled
1951
+ footer, compact, plus the chrome at end of file
1952
+
1953
+ All four regions are optional and any combination lays out correctly: `header` + `footer` is a
1954
+ confirm dialog, `header` + `main` a bare card, `main` alone a grouped chat message. Source
1955
+ order is canonical — figure, header, main, footer — and a variant may re-position regions
1956
+ visually but never asks an author to reorder markup.
1957
+
1958
+ Rather than four arrangements, a variant class sets *only* a density scale on itself
1959
+ (`--usewc-layout-region-padding`, `-gap`, `-avatar-size`, `--usewc-layout-header-rule`,
1960
+ `-footer-rule`, `--usewc-layout-footer-justify`, `--usewc-layout-measure`) and one generic set
1961
+ of region rules reads them. Just three arrangements can't be said in a property — prose's
1962
+ column header, message's rail, message's inline hgroup — and those follow as structural
1963
+ overrides. The mechanic isn't repeated per variant either: a `use-layout.page` is a
1964
+ `use-layout`, so the base pin/grow rules above already cover it.
1965
+
1966
+ Every selector stays compound (`use-layout.page`, never a bare `.page`) so none of these
1967
+ common words can collide with an author's own class of the same name.
1968
+
1969
+ `dialog` and `[popover]` deliberately carry no layout here — they are chrome only
1970
+ (`dialog.css`, `popover.css`). Nest a `use-layout` inside one to lay it out, which keeps the
1971
+ overlay element's UA-controlled `display` alone and lets a dialog host any variant rather
1972
+ than being hard-wired to the card's arrangement.
1973
+
1974
+ Every rule carries the `use-theme-escape` revert so an escaped subtree drops back to
1975
+ unstyled markup, the same as the rest of this family. */
1976
+ @layer use-theme.layout {
1977
+ use-layout {
1944
1978
  display: flex;
1945
1979
  flex-direction: column;
1980
+
1981
+ use-theme-escape & {
1982
+ all: revert-layer;
1983
+ }
1984
+ }
1985
+
1986
+ use-layout[inline] {
1987
+ display: inline-flex;
1988
+
1989
+ use-theme-escape & {
1990
+ all: revert-layer;
1991
+ }
1992
+ }
1993
+
1994
+ use-layout[direction="row"] {
1995
+ flex-direction: row;
1996
+
1997
+ use-theme-escape & {
1998
+ all: revert-layer;
1999
+ }
2000
+ }
2001
+
2002
+ use-layout[wrap] {
2003
+ flex-wrap: wrap;
2004
+
2005
+ use-theme-escape & {
2006
+ all: revert-layer;
2007
+ }
2008
+ }
2009
+
2010
+ use-layout[align="start"] {
2011
+ align-items: start;
2012
+
2013
+ use-theme-escape & {
2014
+ all: revert-layer;
2015
+ }
2016
+ }
2017
+
2018
+ use-layout[align="center"] {
2019
+ align-items: center;
2020
+
2021
+ use-theme-escape & {
2022
+ all: revert-layer;
2023
+ }
2024
+ }
2025
+
2026
+ use-layout[align="end"] {
2027
+ align-items: end;
2028
+
2029
+ use-theme-escape & {
2030
+ all: revert-layer;
2031
+ }
2032
+ }
2033
+
2034
+ use-layout[align="stretch"] {
2035
+ align-items: stretch;
2036
+
2037
+ use-theme-escape & {
2038
+ all: revert-layer;
2039
+ }
2040
+ }
2041
+
2042
+ use-layout[justify="start"] {
2043
+ justify-content: start;
2044
+
2045
+ use-theme-escape & {
2046
+ all: revert-layer;
2047
+ }
2048
+ }
2049
+
2050
+ use-layout[justify="center"] {
2051
+ justify-content: center;
2052
+
2053
+ use-theme-escape & {
2054
+ all: revert-layer;
2055
+ }
2056
+ }
2057
+
2058
+ use-layout[justify="end"] {
2059
+ justify-content: end;
2060
+
2061
+ use-theme-escape & {
2062
+ all: revert-layer;
2063
+ }
2064
+ }
2065
+
2066
+ use-layout[justify="space-between"] {
2067
+ justify-content: space-between;
2068
+
2069
+ use-theme-escape & {
2070
+ all: revert-layer;
2071
+ }
2072
+ }
2073
+
2074
+ use-layout[justify="space-around"] {
2075
+ justify-content: space-around;
2076
+
2077
+ use-theme-escape & {
2078
+ all: revert-layer;
2079
+ }
2080
+ }
2081
+
2082
+ use-layout[gap="xsmall"] {
2083
+ gap: var(--usewc-layout-flex-gap-xsmall);
2084
+
2085
+ use-theme-escape & {
2086
+ all: revert-layer;
2087
+ }
2088
+ }
2089
+
2090
+ use-layout[gap="small"] {
2091
+ gap: var(--usewc-layout-flex-gap-small);
2092
+
2093
+ use-theme-escape & {
2094
+ all: revert-layer;
2095
+ }
2096
+ }
2097
+
2098
+ use-layout[gap="medium"] {
2099
+ gap: var(--usewc-layout-flex-gap-medium);
2100
+
2101
+ use-theme-escape & {
2102
+ all: revert-layer;
2103
+ }
2104
+ }
2105
+
2106
+ use-layout[gap="large"] {
2107
+ gap: var(--usewc-layout-flex-gap-large);
2108
+
2109
+ use-theme-escape & {
2110
+ all: revert-layer;
2111
+ }
2112
+ }
2113
+
2114
+ use-layout[gap="xlarge"] {
2115
+ gap: var(--usewc-layout-flex-gap-xlarge);
2116
+
2117
+ use-theme-escape & {
2118
+ all: revert-layer;
2119
+ }
2120
+ }
2121
+
2122
+ use-layout[gap="super"] {
2123
+ gap: var(--usewc-layout-flex-gap-super);
2124
+
2125
+ use-theme-escape & {
2126
+ all: revert-layer;
2127
+ }
2128
+ }
2129
+
2130
+ use-layout[fill],
2131
+ use-layout > use-layout[fill] {
2132
+ flex: 1 1 auto;
1946
2133
  min-block-size: 0;
2134
+ min-inline-size: 0;
1947
2135
 
1948
2136
  use-theme-escape & {
1949
2137
  all: revert-layer;
1950
2138
  }
1951
2139
  }
1952
2140
 
1953
- use-shell > :not(use-shell-fill),
1954
- dialog:modal:not(.appearance-native) > :not(use-shell-fill),
1955
- use-card > :not(use-shell-fill),
1956
- [popover]:popover-open:not(use-menu, .appearance-native) > :not(use-shell-fill) {
2141
+ use-layout:has(> use-layout[fill]) > :not(use-layout[fill]) {
1957
2142
  flex: 0 0 auto;
1958
2143
  min-block-size: 0;
2144
+ min-inline-size: 0;
1959
2145
 
1960
2146
  use-theme-escape & {
1961
2147
  all: revert-layer;
1962
2148
  }
1963
2149
  }
1964
2150
 
1965
- use-shell-fill {
1966
- display: block;
1967
- flex: 1 1 auto;
2151
+ /* ── Variant density ────────────────────────────────────────────────────────────────────────
2152
+ A variant class sets its own scale and nothing else; the generic region rules below read
2153
+ these properties. Keeping the arrangement in one ruleset rather than four means a future
2154
+ region variant (a tabbed header, a search header, a toolbar) can be written as a plain
2155
+ `header.tabbed` and win on specificity, instead of having to out-specify a
2156
+ `use-layout.card > header`. That's also why every generic rule scopes its container list in
2157
+ `:where()` — it contributes no specificity, so those rules sit at the weight of a bare
2158
+ `header` and are trivially overridden. Between themselves the generic and structural rules
2159
+ order by source position, later winning. */
2160
+ use-layout.page {
2161
+ --usewc-layout-region-padding: var(--usewc-layout-page-padding);
2162
+ --usewc-layout-region-padding-block: var(--usewc-layout-page-padding-block);
2163
+ --usewc-layout-region-gap: var(--usewc-layout-page-gap);
2164
+ --usewc-layout-region-avatar-size: var(--usewc-layout-page-avatar-size);
2165
+ --usewc-layout-header-rule: var(--usewc-color-region-border);
2166
+ --usewc-layout-footer-rule: var(--usewc-color-region-border);
2167
+ --usewc-layout-footer-justify: space-between;
2168
+
2169
+ use-theme-escape & {
2170
+ all: revert-layer;
2171
+ }
2172
+ }
2173
+
2174
+ use-layout.prose {
2175
+ --usewc-layout-region-padding: var(--usewc-layout-prose-padding);
2176
+ --usewc-layout-region-gap: var(--usewc-layout-prose-gap);
2177
+ --usewc-layout-region-avatar-size: var(--usewc-layout-prose-avatar-size);
2178
+ --usewc-layout-measure: var(--usewc-layout-prose-measure);
2179
+ --usewc-layout-footer-rule: var(--usewc-color-region-border);
2180
+ --usewc-layout-footer-justify: flex-start;
2181
+
2182
+ use-theme-escape & {
2183
+ all: revert-layer;
2184
+ }
2185
+ }
2186
+
2187
+ use-layout.message {
2188
+ --usewc-layout-region-padding: var(--usewc-layout-message-padding);
2189
+ --usewc-layout-region-gap: var(--usewc-layout-message-gap);
2190
+ --usewc-layout-region-avatar-size: var(--usewc-layout-message-rail);
2191
+ --usewc-layout-footer-justify: flex-start;
2192
+
2193
+ use-theme-escape & {
2194
+ all: revert-layer;
2195
+ }
2196
+ }
2197
+
2198
+ use-layout.card {
2199
+ --usewc-layout-region-padding: var(--usewc-layout-card-padding);
2200
+ --usewc-layout-region-gap: var(--usewc-layout-card-gap);
2201
+ --usewc-layout-region-avatar-size: var(--usewc-layout-card-avatar-size);
2202
+ --usewc-layout-footer-rule: var(--usewc-color-region-border);
2203
+ --usewc-layout-footer-justify: flex-end;
2204
+
2205
+ use-theme-escape & {
2206
+ all: revert-layer;
2207
+ }
2208
+ }
2209
+
2210
+ /* ── Generic regions ────────────────────────────────────────────────────────────────────────
2211
+ One ruleset for all four variants, reading the properties above. A rule colour left unset
2212
+ falls back to `transparent`, so a variant opts into a hairline by naming one rather than by
2213
+ having a rule of its own. */
2214
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) {
1968
2215
  min-block-size: 0;
2216
+
2217
+ use-theme-escape & {
2218
+ all: revert-layer;
2219
+ }
2220
+ }
2221
+
2222
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > use-layout[fill] {
1969
2223
  overflow: auto;
2224
+ padding: var(--usewc-layout-region-padding);
1970
2225
 
1971
2226
  use-theme-escape & {
1972
2227
  all: revert-layer;
1973
2228
  }
1974
2229
  }
1975
- }
1976
2230
 
1977
- /* Surface regions — header/poster/footer styling shared by the three surface hosts (`dialog`,
1978
- `use-card`, `[popover]`), scoped to them specifically rather than to bare `use-shell`: this is
1979
- a surface-family visual convention (the anchored-gap offset, avatar sizing, trailing-action
1980
- pinning, a footer rule), not something every generic `use-shell` consumer should inherit.
1981
- Everything here rides on top of `shell.css`'s flex mechanic — this only adds padding, gaps,
1982
- and per-region treatment. */
1983
- @layer use-theme.surface {
1984
- /* An anchored surface keeps a small gap from its anchor, the same convention `use-menu`
1985
- follows in `menu.css`. Scoped to `use-anchored`'s direct child — the one reliable "this is
1986
- anchored" signal — so a standalone `[popover]` or modal `<dialog>` elsewhere keeps its UA
1987
- centering. `margin-block` (both edges) so the gap survives a `position-try` flip to the
1988
- other side. The open-state check mirrors the positioning util: `:popover-open` for a
1989
- popover, `[open]` for a `<dialog>`. */
1990
- use-anchored > :is(dialog, [popover]):not(use-menu, .appearance-native):is([open], :popover-open) {
1991
- margin-block: var(--usewc-layout-surface-anchor-offset);
2231
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > main {
2232
+ padding: var(--usewc-layout-region-padding);
2233
+ max-inline-size: var(--usewc-layout-measure, none);
1992
2234
 
1993
2235
  use-theme-escape & {
1994
2236
  all: revert-layer;
1995
2237
  }
1996
2238
  }
1997
2239
 
1998
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > figure {
2240
+ /* The region owns its outer spacing, so the first and last things in it shed the block margins
2241
+ they'd otherwise stack on top of the padding — a leading `<p>`, `<ul>`, `<ol>` or heading
2242
+ would otherwise push itself away from an edge that's already spaced. Matched by position
2243
+ rather than by tag so it holds for whatever flow content lands there. */
2244
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card)
2245
+ > :is(main, use-layout[fill])
2246
+ > :first-child {
2247
+ margin-block-start: 0;
2248
+
2249
+ use-theme-escape & {
2250
+ all: revert-layer;
2251
+ }
2252
+ }
2253
+
2254
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card)
2255
+ > :is(main, use-layout[fill])
2256
+ > :last-child {
2257
+ margin-block-end: 0;
2258
+
2259
+ use-theme-escape & {
2260
+ all: revert-layer;
2261
+ }
2262
+ }
2263
+
2264
+ /* A header already supplies the block-start padding, so the body region drops its own and the
2265
+ two read as one padded column rather than a double gap under the header. */
2266
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card)
2267
+ > header
2268
+ + :is(main, use-layout[fill]) {
2269
+ padding-block-start: 0;
2270
+
2271
+ use-theme-escape & {
2272
+ all: revert-layer;
2273
+ }
2274
+ }
2275
+
2276
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > figure {
1999
2277
  margin: 0;
2000
2278
 
2001
2279
  use-theme-escape & {
@@ -2003,7 +2281,7 @@
2003
2281
  }
2004
2282
  }
2005
2283
 
2006
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > figure > img {
2284
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > figure > img {
2007
2285
  display: block;
2008
2286
  inline-size: 100%;
2009
2287
  block-size: auto;
@@ -2014,22 +2292,40 @@
2014
2292
  }
2015
2293
  }
2016
2294
 
2017
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > header {
2295
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > header {
2296
+ display: flex;
2297
+ align-items: center;
2298
+ justify-content: flex-start;
2299
+ gap: var(--usewc-layout-region-gap);
2300
+ padding-block: var(--usewc-layout-region-padding-block, var(--usewc-layout-region-padding));
2301
+ padding-inline: var(--usewc-layout-region-padding);
2302
+ border-block-end: var(--usewc-layout-surface-border) solid var(--usewc-layout-header-rule, transparent);
2303
+
2304
+ use-theme-escape & {
2305
+ all: revert-layer;
2306
+ }
2307
+ }
2308
+
2309
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > footer {
2018
2310
  display: flex;
2019
2311
  align-items: center;
2020
- gap: var(--usewc-space-surface-gap);
2021
- padding: var(--usewc-space-surface-padding);
2312
+ justify-content: var(--usewc-layout-footer-justify, flex-end);
2313
+ gap: var(--usewc-layout-region-gap);
2314
+ padding-block: var(--usewc-layout-region-padding-block, var(--usewc-layout-region-padding));
2315
+ padding-inline: var(--usewc-layout-region-padding);
2316
+ border-block-start: var(--usewc-layout-surface-border) solid var(--usewc-layout-footer-rule, transparent);
2022
2317
 
2023
2318
  use-theme-escape & {
2024
2319
  all: revert-layer;
2025
2320
  }
2026
2321
  }
2027
2322
 
2028
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > header > img.avatar {
2323
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > header > :is(img.avatar, figure) {
2029
2324
  flex: none;
2030
- inline-size: var(--usewc-layout-surface-avatar-size);
2031
- block-size: var(--usewc-layout-surface-avatar-size);
2325
+ inline-size: var(--usewc-layout-region-avatar-size);
2326
+ block-size: var(--usewc-layout-region-avatar-size);
2032
2327
  border-radius: var(--usewc-effect-surface-avatar-border-radius);
2328
+ overflow: hidden;
2033
2329
  object-fit: cover;
2034
2330
 
2035
2331
  use-theme-escape & {
@@ -2037,8 +2333,14 @@
2037
2333
  }
2038
2334
  }
2039
2335
 
2040
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > header > hgroup {
2041
- flex: 1;
2336
+ /* An `hgroup` is a title stack wherever it turns up in a region — often wrapped in a
2337
+ `<section>` in a topbar — so it's matched by descent rather than as a direct child. Only the
2338
+ one sitting directly in a header grows, since that's what pushes trailing actions to the
2339
+ far end. */
2340
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > :is(header, footer) hgroup {
2341
+ display: flex;
2342
+ flex-direction: column;
2343
+ gap: var(--usewc-layout-region-title-gap);
2042
2344
  min-inline-size: 0;
2043
2345
 
2044
2346
  use-theme-escape & {
@@ -2046,48 +2348,194 @@
2046
2348
  }
2047
2349
  }
2048
2350
 
2049
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native))
2050
- > header
2051
- > *:last-child:not(:first-child) {
2052
- margin-inline-start: auto;
2351
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > header > hgroup {
2352
+ flex: 1;
2053
2353
 
2054
2354
  use-theme-escape & {
2055
2355
  all: revert-layer;
2056
2356
  }
2057
2357
  }
2058
2358
 
2059
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > use-shell-fill {
2060
- padding: var(--usewc-space-surface-padding);
2359
+ /* A header and footer are flex containers and an `hgroup` is a flex column, so `gap` is the
2360
+ spacing — a default block margin stacks on top of it and blows the region apart. Headings are
2361
+ matched at any depth, since a topbar routinely nests them a `<section>` and an `<hgroup>`
2362
+ down; `<p>` and friends only need it where they sit directly in the flex flow. Body copy in
2363
+ `<main>` keeps its margins, which is the whole point of a paragraph there. */
2364
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > :is(header, footer) > *,
2365
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > :is(header, footer) hgroup > *,
2366
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card)
2367
+ > :is(header, footer)
2368
+ :is(h1, h2, h3, h4, h5, h6) {
2369
+ margin-block: 0;
2061
2370
 
2062
2371
  use-theme-escape & {
2063
2372
  all: revert-layer;
2064
2373
  }
2065
2374
  }
2066
2375
 
2067
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > header + use-shell-fill {
2068
- padding-block-start: 0;
2376
+ /* A `<section>` inside a header or footer is one cluster of related controls. Two or more of
2377
+ them means the region has distinct ends, so it splits them apart regardless of the variant's
2378
+ default justification. */
2379
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > :is(header, footer) > section {
2380
+ display: flex;
2381
+ align-items: center;
2382
+ gap: var(--usewc-layout-region-gap);
2069
2383
 
2070
2384
  use-theme-escape & {
2071
2385
  all: revert-layer;
2072
2386
  }
2073
2387
  }
2074
2388
 
2075
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > footer {
2389
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card)
2390
+ > :is(header, footer):has(> section ~ section) {
2391
+ justify-content: space-between;
2392
+
2393
+ use-theme-escape & {
2394
+ all: revert-layer;
2395
+ }
2396
+ }
2397
+
2398
+ /* ── Structural overrides ───────────────────────────────────────────────────────────────────
2399
+ The three arrangements a density property can't express. Same `:where()` weight as the
2400
+ generic rules, winning on source order. */
2401
+
2402
+ /* Prose stacks its header instead of spreading it across a row: an eyebrow, then a title free
2403
+ to wrap, then a byline. It shares `main`'s measure so the two align down the left edge,
2404
+ while a `<figure>` above stays full-bleed to the container. A growing `hgroup` would stretch
2405
+ vertically in a column, so it sizes to content here. */
2406
+ :where(use-layout.prose) > header {
2407
+ flex-direction: column;
2408
+ align-items: start;
2409
+ max-inline-size: var(--usewc-layout-measure);
2410
+
2411
+ use-theme-escape & {
2412
+ all: revert-layer;
2413
+ }
2414
+ }
2415
+
2416
+ :where(use-layout.prose) > header > hgroup {
2417
+ flex: 0 1 auto;
2418
+
2419
+ use-theme-escape & {
2420
+ all: revert-layer;
2421
+ }
2422
+ }
2423
+
2424
+ /* A message is the one variant that isn't a column: a fixed avatar rail, then header, body and
2425
+ reactions stacked beside it. It's a grid rather than nested flex so the rail keeps its width
2426
+ when a grouped message omits its header and leaves the rail empty — bodies stay aligned down
2427
+ the feed either way. The container owns the padding, so the regions drop theirs. */
2428
+ use-layout.message {
2429
+ display: grid;
2430
+ grid-template-columns: var(--usewc-layout-message-rail) 1fr;
2431
+ gap: var(--usewc-layout-region-gap);
2432
+ padding: var(--usewc-layout-region-padding);
2433
+
2434
+ use-theme-escape & {
2435
+ all: revert-layer;
2436
+ }
2437
+ }
2438
+
2439
+ /* The rail is a fixed square that centres whatever it's given — a photo, an `<svg>` glyph, a
2440
+ date badge, a `<div>` of initials — rather than assuming an `<img>`. A photo fills it and
2441
+ crops; anything else keeps its own size and sits in the middle. */
2442
+ :where(use-layout.message) > figure {
2076
2443
  display: flex;
2444
+ grid-column: 1;
2445
+ grid-row: 1 / -1;
2077
2446
  align-items: center;
2078
- gap: var(--usewc-space-surface-gap);
2079
- padding: var(--usewc-space-surface-padding);
2080
- border-block-start: var(--usewc-layout-surface-border) solid var(--usewc-color-surface-footer-border);
2447
+ justify-content: center;
2448
+ inline-size: var(--usewc-layout-message-rail);
2449
+ block-size: var(--usewc-layout-message-rail);
2450
+ overflow: hidden;
2451
+ border-radius: var(--usewc-effect-surface-avatar-border-radius);
2452
+ font-size: calc(var(--usewc-layout-message-rail) * 0.4);
2453
+ line-height: 1;
2081
2454
 
2082
2455
  use-theme-escape & {
2083
2456
  all: revert-layer;
2084
2457
  }
2085
2458
  }
2086
2459
 
2087
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native))
2088
- > footer
2089
- > *:last-child:not(:first-child) {
2090
- margin-inline-start: auto;
2460
+ :where(use-layout.message) > figure > img {
2461
+ block-size: 100%;
2462
+
2463
+ use-theme-escape & {
2464
+ all: revert-layer;
2465
+ }
2466
+ }
2467
+
2468
+ /* A `<figure>` inside the body is an attachment, not the rail — it keeps the flow but sheds the
2469
+ UA margin so the region's own spacing is the only spacing. */
2470
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > :is(main, use-layout[fill]) figure {
2471
+ margin-inline: 0;
2472
+
2473
+ use-theme-escape & {
2474
+ all: revert-layer;
2475
+ }
2476
+ }
2477
+
2478
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card)
2479
+ > :is(main, use-layout[fill])
2480
+ figure
2481
+ > img {
2482
+ display: block;
2483
+ inline-size: 100%;
2484
+ block-size: auto;
2485
+
2486
+ use-theme-escape & {
2487
+ all: revert-layer;
2488
+ }
2489
+ }
2490
+
2491
+ :where(use-layout.message) > :is(header, main, footer) {
2492
+ grid-column: 2;
2493
+ padding: 0;
2494
+ max-inline-size: none;
2495
+
2496
+ use-theme-escape & {
2497
+ all: revert-layer;
2498
+ }
2499
+ }
2500
+
2501
+ /* Name and timestamp sit on one line here rather than stacking, and the line never wraps —
2502
+ trailing actions stay put as the name grows. */
2503
+ :where(use-layout.message) > header > hgroup {
2504
+ flex-direction: row;
2505
+ align-items: baseline;
2506
+ gap: var(--usewc-layout-region-gap);
2507
+ white-space: nowrap;
2508
+
2509
+ use-theme-escape & {
2510
+ all: revert-layer;
2511
+ }
2512
+ }
2513
+
2514
+ /* Card chrome — the one bit of floating surface treatment that isn't layout, and the only
2515
+ thing separating `.card` from the other three variants: they arrange regions, this also
2516
+ paints a raised panel. `.outlined` keeps the border but drops the shadow, for a flatter
2517
+ card.
2518
+
2519
+ A card nested directly in a `dialog` or `[popover]` skips the chrome: the overlay already
2520
+ paints a surface, and stacking a second border and shadow inside it reads as a panel within
2521
+ a panel. That makes `.card` the variant to reach for inside an overlay — a modal wants
2522
+ exactly the card's arrangement (avatar header, actions collected at the inline-end edge),
2523
+ just not a second background. */
2524
+ use-layout.card:not(:is(dialog, [popover]) > *) {
2525
+ overflow: hidden;
2526
+ background: var(--usewc-color-surface-background);
2527
+ border: var(--usewc-layout-surface-border) solid var(--usewc-color-surface-border);
2528
+ border-radius: var(--usewc-effect-surface-border-radius);
2529
+ box-shadow: var(--usewc-effect-surface-shadow);
2530
+ color: var(--usewc-color-document-text);
2531
+
2532
+ use-theme-escape & {
2533
+ all: revert-layer;
2534
+ }
2535
+ }
2536
+
2537
+ use-layout.card.outlined:not(:is(dialog, [popover]) > *) {
2538
+ box-shadow: none;
2091
2539
 
2092
2540
  use-theme-escape & {
2093
2541
  all: revert-layer;
package/dist/tokens.css CHANGED
@@ -639,10 +639,48 @@
639
639
  --usewc-color-code-border: var(--usewc-color-transparent);
640
640
  --usewc-font-code-size: var(--usewc-font-size-14);
641
641
 
642
- /* Surface — shared by every surface host (dialog, use-card, [popover]): the visible chrome
643
- (background/border/radius/shadow), the backdrop behind a modal `<dialog>` (non-modal
644
- `[popover]` keeps its transparent UA backdrop), the gap an anchored surface keeps from its
645
- anchor, and the internal header/poster/footer region layout they all apply the same way. */
642
+ /* Flex layout — `use-layout`'s named gap scale (see `theme/layout.css`). */
643
+ --usewc-layout-flex-gap-xsmall: var(--usewc-space-2);
644
+ --usewc-layout-flex-gap-small: var(--usewc-space-4);
645
+ --usewc-layout-flex-gap-medium: var(--usewc-space-8);
646
+ --usewc-layout-flex-gap-large: var(--usewc-space-16);
647
+ --usewc-layout-flex-gap-xlarge: var(--usewc-space-24);
648
+ --usewc-layout-flex-gap-super: var(--usewc-space-32);
649
+
650
+ /* Layout regions — the density scale each `use-layout` variant sets on itself, which the one
651
+ generic set of header / main / footer rules in `theme/layout.css` then reads. The four
652
+ variants are deliberately far apart: a document and a card in a feed can't share one
653
+ padding step and still read as themselves, so each gets its own rather than overriding a
654
+ shared base. The hairline colour is shared, since a rule is a rule wherever it lands. */
655
+ --usewc-color-region-border: var(--usewc-color-surface-border);
656
+ --usewc-layout-region-title-gap: var(--usewc-space-2);
657
+
658
+ --usewc-layout-prose-padding: var(--usewc-space-32);
659
+ --usewc-layout-prose-gap: var(--usewc-space-16);
660
+ --usewc-layout-prose-measure: 68ch;
661
+ --usewc-layout-prose-avatar-size: var(--usewc-space-24);
662
+
663
+ --usewc-layout-page-padding: var(--usewc-space-20);
664
+ --usewc-layout-page-padding-block: var(--usewc-space-12);
665
+ --usewc-layout-page-gap: var(--usewc-space-12);
666
+ --usewc-layout-page-avatar-size: var(--usewc-space-32);
667
+
668
+ --usewc-layout-card-padding: var(--usewc-space-12);
669
+ --usewc-layout-card-gap: var(--usewc-space-8);
670
+ --usewc-layout-card-avatar-size: var(--usewc-space-24);
671
+
672
+ /* The message rail width and its avatar are one value on purpose: a grouped message omits its
673
+ header and leaves the rail empty, and the bodies only stay aligned down a feed if the empty
674
+ rail is exactly as wide as the avatar it replaces. */
675
+ --usewc-layout-message-padding: var(--usewc-space-12);
676
+ --usewc-layout-message-gap: var(--usewc-space-12);
677
+ --usewc-layout-message-rail: var(--usewc-space-32);
678
+
679
+ /* Surface — the visible chrome for a surface host (background/border/radius/shadow), the
680
+ backdrop behind a modal `<dialog>` (non-modal `[popover]` keeps its transparent UA
681
+ backdrop), and the gap an anchored surface keeps from its anchor. `-padding` is the inset a
682
+ `dialog`/`[popover]` keeps from the viewport edge when sizing itself, not region padding —
683
+ the regions inside one come from whichever `use-layout` variant is nested there. */
646
684
  --usewc-color-surface-background: var(--usewc-color-canvas-background);
647
685
  --usewc-color-surface-border: var(--usewc-color-button-base-border-static);
648
686
  --usewc-color-surface-backdrop: oklch(0% 0 0 / 0.4);
@@ -651,11 +689,8 @@
651
689
  --usewc-effect-surface-border-radius: var(--usewc-effect-border-radius-8);
652
690
  --usewc-effect-surface-shadow: var(--usewc-effect-shadow-raised);
653
691
  --usewc-effect-surface-transition-duration: 200ms;
654
- --usewc-space-surface-padding: var(--usewc-space-16);
655
- --usewc-space-surface-gap: var(--usewc-space-12);
656
- --usewc-layout-surface-avatar-size: var(--usewc-space-32);
692
+ --usewc-layout-surface-padding: var(--usewc-space-16);
657
693
  --usewc-effect-surface-avatar-border-radius: var(--usewc-effect-border-radius-full);
658
- --usewc-color-surface-footer-border: var(--usewc-color-surface-border);
659
694
  }
660
695
 
661
696
  @media (prefers-color-scheme: dark) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-wc",
3
- "version": "1.0.0-pre.64",
3
+ "version": "1.0.0-pre.66",
4
4
  "private": false,
5
5
  "homepage": "https://usewc.com",
6
6
  "bugs": "https://github.com/dang-rabbits/use-wc/issues",