Skip to content

Commit 2669279

Browse files
committed
Add new utility css classes.
1 parent fd38e2d commit 2669279

11 files changed

Lines changed: 111 additions & 53 deletions

File tree

‎frontend/docs/componentGuide.ts‎

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function defineComponentGuideComponents() {
2828
html`<header class="component-header">
2929
<div class="component-header__top">
3030
<div class="component-header__identity">
31-
<code>{{ name }}</code><span>{{ kind }}</span>
31+
<code>{{ name }}</code><span class="tracking-wide">{{ kind }}</span>
3232
</div>
3333
<nav class="component-header__links" aria-label="Component resources">
3434
<a href="#api-reference">API <span aria-hidden="true">↗</span></a>
@@ -52,7 +52,7 @@ export function defineComponentGuideComponents() {
5252
ComponentExample: defineComponent<ComponentExample>(
5353
html`<section class="component-example" :aria-label="label">
5454
<header class="component-example__heading">
55-
<span class="component-example__dot" aria-hidden="true"></span><strong>{{ label }}</strong><span class="component-example__tag">COMPOSITION</span>
55+
<span class="component-example__dot" aria-hidden="true"></span><strong>{{ label }}</strong><span class="component-example__tag tracking-wide">COMPOSITION</span>
5656
</header>
5757
<div class="component-example__stage"><slot></slot></div>
5858
<p class="component-example__caption">{{ caption }}</p>
@@ -256,7 +256,6 @@ function registerComponentHeaderStyles(root: Style, palette: ThemePalette) {
256256
root
257257
.select('.component-header__identity span, .component-example__tag')
258258
.fontSize('0.65rem')
259-
.letterSpacing('0.1em')
260259
.textTransform('uppercase')
261260
.color(neutral.text.subtle)
262261

@@ -579,25 +578,7 @@ function registerLandingIntroductionStyles(root: Style) {
579578

580579
root
581580
.select('.landing-section-demo .landing-section__title')
582-
.maxWidth('20ch')
583581
.fontSize('clamp(1.75rem, 5cqi, 3.25rem)')
584-
.letterSpacing('-0.04em')
585-
.textWrap('balance')
586-
587-
root
588-
.select('.landing-section-demo .landing-section__subtitle')
589-
.maxWidth('52ch')
590-
.letterSpacing('normal')
591-
.color('inherit')
592-
.opacity('0.78')
593-
.textWrap('pretty')
594-
595-
root
596-
.select('.landing-section-demo .landing-section__eyebrow')
597-
.display('inline-flex')
598-
.alignItems('center')
599-
.gap('0.75rem')
600-
.color('inherit')
601582

602583
root
603584
.select('.landing-section-demo .landing-section__eyebrow::before')
@@ -606,10 +587,6 @@ function registerLandingIntroductionStyles(root: Style) {
606587
.height('2px')
607588
.background('currentColor')
608589
.flexShrink('0')
609-
610-
root
611-
.select('.landing-section-demo .landing-section__actions:empty')
612-
.display('none')
613590
}
614591

615592
function registerLandingFeatureStyles(root: Style, palette: ThemePalette) {
@@ -638,7 +615,6 @@ function registerLandingFeatureStyles(root: Style, palette: ThemePalette) {
638615
.fontSize('1.0625rem')
639616
.fontWeight('650')
640617
.lineHeight('1.35')
641-
.letterSpacing('-0.02em')
642618

643619
root
644620
.select('.landing-section-demo__feature p')
@@ -665,7 +641,6 @@ function registerLandingWorkflowStyles(root: Style, palette: ThemePalette) {
665641
.select('.landing-section-demo__workflow .text-eyebrow')
666642
.margin('0')
667643
.fontSize('0.625rem')
668-
.letterSpacing('0.1em')
669644

670645
root
671646
.select('.landing-section-demo__step')

‎frontend/purestack.studio/components/landing/landing-section/_content-section.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
>
77
<Panel class="landing-section-demo__feature" variant="surface">
88
<IconFrame name="lucide:layers" tone="accent" variant="surface"/>
9-
<h3>Start with good foundations</h3>
9+
<h3 class="tracking-tight text-balance">Start with good foundations</h3>
1010
<p>Reusable components give every page a familiar structure.</p>
1111
</Panel>
1212
<Panel class="landing-section-demo__feature" variant="surface">
1313
<IconFrame name="lucide:sparkles" tone="secondary" variant="surface"/>
14-
<h3>Finish with your own details</h3>
14+
<h3 class="tracking-tight text-balance">Finish with your own details</h3>
1515
<p>Your content and ideas give the experience its character.</p>
1616
</Panel>
1717
</LandingSection>

‎frontend/purestack.studio/components/landing/landing-section/_feature-grid.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@
1010
>
1111
<Panel class="landing-section-demo__feature" variant="surfaceAlt">
1212
<IconFrame name="lucide:layers" tone="accent" variant="surface"/>
13-
<h3>Content with structure</h3>
13+
<h3 class="tracking-tight text-balance">Content with structure</h3>
1414
<p>
1515
Give your ideas a home with pages that are easy to write and maintain.
1616
</p>
1717
</Panel>
1818
<Panel class="landing-section-demo__feature" variant="surfaceAlt">
1919
<IconFrame name="lucide:palette" tone="secondary" variant="surface"/>
20-
<h3>A consistent design</h3>
20+
<h3 class="tracking-tight text-balance">A consistent design</h3>
2121
<p>
2222
Make every detail feel connected, from the first heading to the final
2323
link.
2424
</p>
2525
</Panel>
2626
<Panel class="landing-section-demo__feature" variant="surfaceAlt">
2727
<IconFrame name="lucide:mouse-pointer-2" tone="info" variant="surface"/>
28-
<h3>Room to make it yours</h3>
28+
<h3 class="tracking-tight text-balance">Room to make it yours</h3>
2929
<p>
3030
Add the interactions your visitors need, exactly where they need them.
3131
</p>

‎frontend/purestack.studio/components/landing/landing-section/_split-section.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
variant="surface"
2525
>
2626
<Flex justify="between" align="center" wrap="true">
27-
<span class="text-eyebrow">YOUR NEXT RELEASE</span>
27+
<span class="text-eyebrow tracking-wide">YOUR NEXT RELEASE</span>
2828
<Badge tone="success">Ready to build</Badge>
2929
</Flex>
3030
<div class="landing-section-demo__step">

‎frontend/purestack.studio/guides/styling/typography.mdx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ Use utility classes when a single element needs a particular look, for example a
123123
- `fw-100` through `fw-900` set the weight.
124124
- `lh-0` through `lh-7` set the line height from `1.0` to `1.7`.
125125

126+
For deliberate adjustments, use `tracking-normal`, `tracking-tight`, or
127+
`tracking-wide`. Use `text-balance` for headings and `text-pretty` for supporting
128+
copy, with `max-w-heading` or `max-w-prose` to limit line length. `tabular-nums`
129+
keeps digits equally wide when the font supports it, which helps metrics and
130+
changing counters. `text-inherit` keeps a text role's color aligned with its
131+
parent surface.
132+
126133
`fs-*` classes change the **size only**. They do not apply the derived line height, so large text keeps the line height of its surroundings. For multiline text at a large size, add a matching `lh-*` class, as the specimen above does:
127134

128135
```mdx

‎frontend/purestack.studio/guides/styling/utility-css-classes.mdx‎

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@ nav:
1616
PureStack generates a small vocabulary of reusable CSS classes alongside its component styles. Use them in MDX markup, in a component's `class` or `bodyClass` prop, or in a TypeScript `html` template. They are generated for each configured theme, so classes that use palette colors or font sizes follow the active skin.
1717

1818
<Panel tone="info" bodyClass="p-3" class="mb-5">
19-
<p class="text-eyebrow mt-0">Release note</p>
20-
<h2 class="mt-0 mb-2">A clearer way to build</h2>
21-
<p class="mb-0">Spacing and type follow the site's theme.</p>
19+
<p class="text-eyebrow text-inherit tracking-wide mt-0">Release note</p>
20+
<h2 class="max-w-heading text-balance tracking-tight mt-0 mb-2">
21+
A clearer way to build
22+
</h2>
23+
<p class="max-w-prose text-pretty tracking-normal mb-0">
24+
Spacing and type follow the site's theme.
25+
</p>
2226
</Panel>
2327

2428
```mdx
2529
<Panel tone="info" bodyClass="p-3" class="mb-5">
26-
<p class="text-eyebrow mt-0">Release note</p>
27-
<h2 class="mt-0 mb-2">A clearer way to build</h2>
28-
<p class="mb-0">Spacing and type follow the site's theme.</p>
30+
<p class="text-eyebrow text-inherit tracking-wide mt-0">Release note</p>
31+
<h2 class="max-w-heading text-balance tracking-tight mt-0 mb-2">A clearer way to build</h2>
32+
<p class="max-w-prose text-pretty tracking-normal mb-0">Spacing and type follow the site's theme.</p>
2933
</Panel>
3034
```
3135

32-
Here `bodyClass` styles the Panel's body, while `class` styles the elements inside it. This distinction matters for components with their own wrapper and internal content. The [component catalog](/components/) shows which props each component exposes.
36+
Here `bodyClass` styles the Panel's inner body, while `class` styles its root. This distinction matters for components with their own wrapper and internal content. The [component catalog](/components/) shows which props each component exposes.
3337

3438
The class names below come from PureStack's utility and component style generators. They are not Tailwind-style arbitrary values: a class works when the generator defines it. For a repeated design pattern, add a named style in your site's TypeScript styles; use utilities for local composition and small adjustments.
3539

@@ -86,10 +90,29 @@ The other numeric type controls are `fw-100` through `fw-900`, in steps of 100,
8690
| --- | --- |
8791
| `text-left`, `text-center`, `text-right` | Physical text alignment. |
8892
| `text-start`, `text-end`, `text-justify` | Logical start/end alignment or justified text. |
93+
| `text-balance` | Balances line lengths in short text, such as a heading, using `text-wrap: balance`. |
94+
| `text-pretty` | Improves paragraph line breaks using `text-wrap: pretty`. |
95+
| `text-inherit` | Inherits the parent's text color, including on a colored surface. |
96+
| `tracking-normal`, `tracking-tight`, `tracking-wide` | Set letter spacing to `normal`, `-0.025em`, or `0.1em`. |
97+
| `tabular-nums` | Requests equal-width digits from the font with `font-variant-numeric: tabular-nums`. Useful for metrics, prices, tables, and counters. |
8998
| `uppercase`, `lowercase` | Text transform. |
9099
| `underline-hover`, `underline-active` | Underline on hover or on active interaction; `underline-active` also recognizes an `.active` class. |
91100
| `text-ellipsis` | Sets `text-overflow: ellipsis` only. Combine it with a constrained width, `overflow-hidden`, and `ws-nowrap` for a single-line ellipsis. |
92101

102+
Wrapping, tracking, inherited color, and numeric utilities override the
103+
corresponding declaration from a text role. They leave its other properties
104+
alone. `tracking-normal`, for example, resets a label's letter spacing without
105+
changing its size or weight. Tabular digits depend on support in the selected
106+
font.
107+
108+
Combine wrapping with a line-length limit when introducing a section:
109+
110+
```mdx
111+
<h2 class="max-w-heading text-balance tracking-tight">A thoughtful foundation for your next idea</h2>
112+
<p class="max-w-prose text-pretty tracking-normal">Keep supporting copy readable without forcing the rest of the layout to use the same width.</p>
113+
<strong class="fs-h1 tabular-nums">1,248</strong>
114+
```
115+
93116
PureStack also has editorial role classes. They set a combination of font size, weight, color, transform, line height, and/or margin, depending on the role:
94117

95118
| Role | Current treatment |
@@ -140,6 +163,18 @@ For any name in this table, `hidden-{name}` hides at widths **up to and includin
140163
<p class="hidden-lg-up">Shown below 1024px</p>
141164
```
142165

166+
`hidden-empty` hides an element only while it matches `:empty`. Use it for
167+
optional action rows or footer containers:
168+
169+
```html
170+
<span class="flex gap-3 hidden-empty"><slot name="actions"></slot></span>
171+
```
172+
173+
An empty container is hidden; adding content restores its normal display.
174+
Comments do not count as content, but literal whitespace text does, so keep an
175+
unused wrapper free of spaces or newlines inside it. The utility does not hide
176+
text-only content. It can be combined with `d-flex` or `d-grid`.
177+
143178
There is no general `md:p-3` or `md:d-flex` syntax in this utility set. Responsive Flex classes and the `Grid` component have their own explicit APIs, described next.
144179

145180
## Flex and Grid composition
@@ -183,6 +218,8 @@ For grid children, `justify-self-start`, `justify-self-center`, `justify-self-en
183218
| --- | --- |
184219
| `w-full`, `h-full` | `width: 100%` or `height: 100%`. |
185220
| `max-w-full` | Caps width at `100%`; combine with a fixed width to fit a smaller container. |
221+
| `max-w-heading` | Limits line length to `20ch`, useful for short headings. |
222+
| `max-w-prose` | Limits line length to `65ch`, useful for paragraphs and introductions. |
186223
| `min-w-0` | Allows a flex or grid child to shrink below its content's preferred width. |
187224
| `max-h-inspector` | Limits height to `24rem`. |
188225
| `overflow-visible`, `overflow-auto`, `overflow-hidden` | Set overflow on both axes. |
@@ -197,6 +234,11 @@ For grid children, `justify-self-start`, `justify-self-center`, `justify-self-en
197234

198235
`auto-fit` is a specialized width utility that sets `width: 1%`; it is not a CSS Grid `auto-fit` track rule. Use it only when that width behavior is intended.
199236

237+
The text-width utilities set only `max-width`, not `width`. The `ch` unit is
238+
based on the font's zero glyph, so the resulting width follows the type size.
239+
They add no padding, centering, or responsive variants; use `mx-auto` when a
240+
block should be centered.
241+
200242
## Borders, radius, and opacity
201243

202244
Border width classes use pixels. `b-1`, `b-2`, and `b-3` set a solid border on all sides. `bx-*`, `by-*`, `bt-*`, `br-*`, `bb-*`, and `bl-*` set the specified edges; `0` removes a width on those edges. For example, `bb-1` adds a solid one-pixel bottom border, and `b-0` removes all border widths.

‎packages/ts-components/src/standard/landing/featureCard.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,27 @@ export function defineFeatureCardComponent() {
2828
<Flex direction="column" align="start" class="feature-card__content gap-3">
2929
<p
3030
r-if="eyebrow"
31-
class="feature-card__eyebrow fs-xxs fw-700 uppercase opacity-75 m-0"
31+
class="feature-card__eyebrow fs-xxs fw-700 uppercase tracking-wide opacity-75 m-0"
3232
>
3333
{{ eyebrow }}
3434
</p>
3535
<h3
3636
r-if="title"
37-
class="feature-card__title fs-h4 fw-600 lh-2 m-0 wrap-anywhere"
37+
class="feature-card__title fs-h4 fw-600 lh-2 m-0 wrap-anywhere tracking-tight text-balance"
3838
>
3939
{{ title }}
4040
</h3>
41-
<p r-if="summary" class="feature-card__summary fs-body lh-6 opacity-75 m-0">
41+
<p
42+
r-if="summary"
43+
class="feature-card__summary fs-body lh-6 tracking-normal text-pretty opacity-75 m-0"
44+
>
4245
{{ summary }}
4346
</p>
4447
<slot></slot>
4548
</Flex>
46-
<Flex wrap="true" align="center" class="feature-card__actions mt-auto gap-3">
47-
<slot name="actions"></slot>
48-
</Flex>
49+
<span
50+
class="feature-card__actions flex flex-wrap align-center mt-auto gap-3 hidden-empty"
51+
><slot name="actions"></slot></span>
4952
</Panel>`
5053

5154
return {

‎packages/ts-components/src/standard/landing/landingSection.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ export function defineLandingSectionComponent() {
1818
:title="title"
1919
:subtitle="subtitle"
2020
:titleTag="titleTag || 'h2'"
21-
eyebrowClass="landing-section__eyebrow m-0 fs-xxs opacity-75"
22-
titleClass="landing-section__title m-0 fw-600 wrap-anywhere"
23-
subtitleClass="landing-section__subtitle m-0 fs-body lh-6"
21+
eyebrowClass="landing-section__eyebrow d-inline-flex align-center gap-3 m-0 fs-xxs opacity-75 tracking-wide text-inherit"
22+
titleClass="landing-section__title m-0 fw-600 wrap-anywhere max-w-heading tracking-tight text-balance"
23+
subtitleClass="landing-section__subtitle m-0 fs-body lh-6 max-w-prose tracking-normal text-inherit text-pretty opacity-75"
2424
>
25-
<div class="landing-section__actions d-flex flex-wrap align-center gap-3">
26-
<slot name="actions"></slot>
27-
</div>
25+
<span
26+
class="landing-section__actions flex flex-wrap align-center gap-3 hidden-empty"
27+
><slot name="actions"></slot></span>
2828
</SectionHeader>
2929
<Grid
3030
class="landing-section__content gap-5"

‎packages/ts-components/src/standard/landing/metricItem.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function defineMetricItemComponent() {
2020
</Flex>
2121
<strong
2222
r-if="value !== undefined && value !== null && value !== ''"
23-
class="metric-item__value fs-h1 fw-600 lh-1 m-0 wrap-anywhere"
23+
class="metric-item__value fs-h1 fw-600 lh-1 m-0 wrap-anywhere tabular-nums"
2424
>
2525
{{ value }}
2626
</strong>

‎packages/ts-style/README.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ Use `themes` and `registerSkin` to configure a site's palette, or
88
`styleBuilder` to collect and render CSS for a theme. PureStack's component
99
library and site generator use these same exports.
1010

11+
Text utilities include `text-balance`, `text-pretty`, `text-inherit`, and
12+
`tabular-nums`. Letter spacing uses `tracking-normal` (`normal`),
13+
`tracking-tight` (`-0.025em`), or `tracking-wide` (`0.1em`). Use `max-w-heading`
14+
(`20ch`) and `max-w-prose` (`65ch`) to limit line length. `hidden-empty` hides
15+
containers matching `:empty`; comments are allowed, but whitespace text counts
16+
as content. These utilities have no generated responsive or hover variants.
17+
1118
Every SSG builder owns an independent style context. The generator propagates
1219
that context across asynchronous factories, hooks, page rendering, and asset
1320
writing, so concurrent sites cannot overwrite each other's palettes or CSS.

0 commit comments

Comments
 (0)