use-wc 1.0.0-pre.65 → 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,104 +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>`.
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);
1990
2234
 
1991
- The `max-block-size` override is what lets it flip on scroll instead of shrinking.
1992
- `popover.css` / `dialog.css` cap the surface at `min(80dvh, 100% - <pad>)`, where `100%`
1993
- is the free space on the anchor's *current* side — so as the anchor nears a viewport edge
1994
- that cap shrinks with it, the surface never overflows, and `position-try` never sees the
1995
- overflow it needs to flip (or to flip back). Reworking the cap to
1996
- `min(80dvh, max(45dvh, 100% - <pad>))` keeps the surface willing to be at least 45dvh: a
1997
- side smaller than that overflows and triggers a flip, and since sibling sides sum to nearly
1998
- the full viewport a 45dvh surface always fits the other one, so the flip lands on-screen.
1999
- Where a side has more than 45dvh the `100%` term still tracks it, up to 80dvh. Left at the
2000
- default `position-try-order` (not `most-block-size`) so the surface stays on the block-end
2001
- (below) side whenever the content fits there, only flipping above when it doesn't — rather
2002
- than jumping to whichever side merely has more room. `overflow` (otherwise `hidden`)
2003
- scrolls the content once the cap bites; a `use-shell-fill` layout already scrolls its own
2004
- fill region (`shell.css`), so the surface itself never overflows there and this `overflow`
2005
- is inert. */
2006
- use-anchored > :is(dialog, [popover]):not(use-menu, .appearance-native):is([open], :popover-open) {
2007
- margin-block: var(--usewc-layout-surface-anchor-offset);
2008
- max-block-size: min(80dvh, max(45dvh, 100% - var(--usewc-space-surface-padding) * 2));
2009
- overflow: auto;
2235
+ use-theme-escape & {
2236
+ all: revert-layer;
2237
+ }
2238
+ }
2239
+
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;
2010
2258
 
2011
2259
  use-theme-escape & {
2012
2260
  all: revert-layer;
2013
2261
  }
2014
2262
  }
2015
2263
 
2016
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > figure {
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 {
2017
2277
  margin: 0;
2018
2278
 
2019
2279
  use-theme-escape & {
@@ -2021,7 +2281,7 @@
2021
2281
  }
2022
2282
  }
2023
2283
 
2024
- :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 {
2025
2285
  display: block;
2026
2286
  inline-size: 100%;
2027
2287
  block-size: auto;
@@ -2032,22 +2292,40 @@
2032
2292
  }
2033
2293
  }
2034
2294
 
2035
- :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 {
2036
2310
  display: flex;
2037
2311
  align-items: center;
2038
- gap: var(--usewc-space-surface-gap);
2039
- 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);
2040
2317
 
2041
2318
  use-theme-escape & {
2042
2319
  all: revert-layer;
2043
2320
  }
2044
2321
  }
2045
2322
 
2046
- :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) {
2047
2324
  flex: none;
2048
- inline-size: var(--usewc-layout-surface-avatar-size);
2049
- 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);
2050
2327
  border-radius: var(--usewc-effect-surface-avatar-border-radius);
2328
+ overflow: hidden;
2051
2329
  object-fit: cover;
2052
2330
 
2053
2331
  use-theme-escape & {
@@ -2055,8 +2333,14 @@
2055
2333
  }
2056
2334
  }
2057
2335
 
2058
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > header > hgroup {
2059
- 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);
2060
2344
  min-inline-size: 0;
2061
2345
 
2062
2346
  use-theme-escape & {
@@ -2064,48 +2348,194 @@
2064
2348
  }
2065
2349
  }
2066
2350
 
2067
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native))
2068
- > header
2069
- > *:last-child:not(:first-child) {
2070
- margin-inline-start: auto;
2351
+ :where(use-layout.page, use-layout.prose, use-layout.message, use-layout.card) > header > hgroup {
2352
+ flex: 1;
2071
2353
 
2072
2354
  use-theme-escape & {
2073
2355
  all: revert-layer;
2074
2356
  }
2075
2357
  }
2076
2358
 
2077
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > use-shell-fill {
2078
- 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;
2079
2370
 
2080
2371
  use-theme-escape & {
2081
2372
  all: revert-layer;
2082
2373
  }
2083
2374
  }
2084
2375
 
2085
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > header + use-shell-fill {
2086
- 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);
2383
+
2384
+ use-theme-escape & {
2385
+ all: revert-layer;
2386
+ }
2387
+ }
2388
+
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);
2087
2433
 
2088
2434
  use-theme-escape & {
2089
2435
  all: revert-layer;
2090
2436
  }
2091
2437
  }
2092
2438
 
2093
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native)) > footer {
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 {
2094
2443
  display: flex;
2444
+ grid-column: 1;
2445
+ grid-row: 1 / -1;
2095
2446
  align-items: center;
2096
- gap: var(--usewc-space-surface-gap);
2097
- padding: var(--usewc-space-surface-padding);
2098
- 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;
2099
2454
 
2100
2455
  use-theme-escape & {
2101
2456
  all: revert-layer;
2102
2457
  }
2103
2458
  }
2104
2459
 
2105
- :is(dialog:not(.appearance-native), use-card, [popover]:not(use-menu, .appearance-native))
2106
- > footer
2107
- > *:last-child:not(:first-child) {
2108
- 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;
2109
2539
 
2110
2540
  use-theme-escape & {
2111
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.65",
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",