Add safe alignment utilities (#14607)

Fixes #7929 and #12916

This PR adds [safe alignment
utilities](https://www.stefanjudis.com/today-i-learned/safe-unsafe-alignment-in-css-flexbox/)
to Tailwind 4. I opted to include them for every justify/align
content/items/self property, but only for the `end` and `center` values.
I know that it doesn't make sense for `start` (as the point of safe
alignment is to fall back to `start` when it overflows), but I'm not
sure about `space-between`, `space-around`, or other values. I certainly
never encountered a situation where I needed `safe` with those.
This commit is contained in:
Benoît Rouleau 2025-03-17 09:09:15 -04:00 • committed by GitHub
parent 1d2d50ee66
commit ebfde316e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 147 additions and 1 deletions

View file

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Experimental_: Add `items-baseline-last` utility ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
- _Experimental_: Add `pointer-none`, `pointer-coarse`, and `pointer-fine` variant ([#16946](https://github.com/tailwindlabs/tailwindcss/pull/16946))
- _Experimental_: Add `any-pointer-none`, `any-pointer-coarse`, and `any-pointer-fine` variants ([#16941](https://github.com/tailwindlabs/tailwindcss/pull/16941))
- _Experimental_: Add safe alignment utilities ([#14607](https://github.com/tailwindlabs/tailwindcss/pull/14607))
- _Experimental_: Add `user-valid` and `user-invalid` variants ([#12370](https://github.com/tailwindlabs/tailwindcss/pull/12370))
- _Experimental_: Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
- _Experimental_: Add `@source inline(…)` ([#17147](https://github.com/tailwindlabs/tailwindcss/pull/17147))

View file

@ -3746,7 +3746,9 @@ exports[`getClassList 1`] = `
"content-baseline",
"content-between",
"content-center",
"content-center-safe",
"content-end",
"content-end-safe",
"content-evenly",
"content-none",
"content-normal",
@ -4877,24 +4879,32 @@ exports[`getClassList 1`] = `
"items-baseline",
"items-baseline-last",
"items-center",
"items-center-safe",
"items-end",
"items-end-safe",
"items-start",
"items-stretch",
"justify-around",
"justify-baseline",
"justify-between",
"justify-center",
"justify-center-safe",
"justify-end",
"justify-end-safe",
"justify-evenly",
"justify-items-center",
"justify-items-center-safe",
"justify-items-end",
"justify-items-end-safe",
"justify-items-normal",
"justify-items-start",
"justify-items-stretch",
"justify-normal",
"justify-self-auto",
"justify-self-center",
"justify-self-center-safe",
"justify-self-end",
"justify-self-end-safe",
"justify-self-start",
"justify-self-stretch",
"justify-start",
@ -6005,18 +6015,24 @@ exports[`getClassList 1`] = `
"place-content-baseline",
"place-content-between",
"place-content-center",
"place-content-center-safe",
"place-content-end",
"place-content-end-safe",
"place-content-evenly",
"place-content-start",
"place-content-stretch",
"place-items-baseline",
"place-items-center",
"place-items-center-safe",
"place-items-end",
"place-items-end-safe",
"place-items-start",
"place-items-stretch",
"place-self-auto",
"place-self-center",
"place-self-center-safe",
"place-self-end",
"place-self-end-safe",
"place-self-start",
"place-self-stretch",
"placeholder-current",
@ -7280,7 +7296,9 @@ exports[`getClassList 1`] = `
"self-auto",
"self-baseline",
"self-center",
"self-center-safe",
"self-end",
"self-end-safe",
"self-start",
"self-stretch",
"sepia",

View file

@ -2,6 +2,7 @@ export const enableBaselineLast = process.env.FEATURES_ENV !== 'stable'
export const enableDetailsContent = process.env.FEATURES_ENV !== 'stable'
export const enableInvertedColors = process.env.FEATURES_ENV !== 'stable'
export const enablePointerVariants = process.env.FEATURES_ENV !== 'stable'
export const enableSafeAlignment = process.env.FEATURES_ENV !== 'stable'
export const enableScripting = process.env.FEATURES_ENV !== 'stable'
export const enableSourceInline = process.env.FEATURES_ENV !== 'stable'
export const enableUserValid = process.env.FEATURES_ENV !== 'stable'

View file

@ -7125,8 +7125,10 @@ test('place-content', async () => {
expect(
await run([
'place-content-center',
'place-content-center-safe',
'place-content-start',
'place-content-end',
'place-content-end-safe',
'place-content-between',
'place-content-around',
'place-content-evenly',
@ -7150,10 +7152,18 @@ test('place-content', async () => {
place-content: center;
}
.place-content-center-safe {
place-content: safe center;
}
.place-content-end {
place-content: end;
}
.place-content-end-safe {
place-content: safe end;
}
.place-content-evenly {
place-content: space-evenly;
}
@ -7194,7 +7204,9 @@ test('place-items', async () => {
await run([
'place-items-start',
'place-items-end',
'place-items-end-safe',
'place-items-center',
'place-items-center-safe',
'place-items-baseline',
'place-items-stretch',
]),
@ -7207,10 +7219,18 @@ test('place-items', async () => {
place-items: center;
}
.place-items-center-safe {
place-items: safe center;
}
.place-items-end {
place-items: end;
}
.place-items-end-safe {
place-items: safe end;
}
.place-items-start {
place-items: start;
}
@ -7241,8 +7261,10 @@ test('align-content', async () => {
await run([
'content-normal',
'content-center',
'content-center-safe',
'content-start',
'content-end',
'content-end-safe',
'content-between',
'content-around',
'content-evenly',
@ -7266,10 +7288,18 @@ test('align-content', async () => {
align-content: center;
}
.content-center-safe {
align-content: safe center;
}
.content-end {
align-content: flex-end;
}
.content-end-safe {
align-content: safe flex-end;
}
.content-evenly {
align-content: space-evenly;
}
@ -7316,7 +7346,9 @@ test('items', async () => {
await run([
'items-start',
'items-end',
'items-end-safe',
'items-center',
'items-center-safe',
'items-baseline',
'items-baseline-last',
'items-stretch',
@ -7334,10 +7366,18 @@ test('items', async () => {
align-items: center;
}
.items-center-safe {
align-items: safe center;
}
.items-end {
align-items: flex-end;
}
.items-end-safe {
align-items: safe flex-end;
}
.items-start {
align-items: flex-start;
}
@ -7371,7 +7411,9 @@ test('justify', async () => {
'justify-normal',
'justify-start',
'justify-end',
'justify-end-safe',
'justify-center',
'justify-center-safe',
'justify-between',
'justify-around',
'justify-evenly',
@ -7390,10 +7432,18 @@ test('justify', async () => {
justify-content: center;
}
.justify-center-safe {
justify-content: safe center;
}
.justify-end {
justify-content: flex-end;
}
.justify-end-safe {
justify-content: safe flex-end;
}
.justify-evenly {
justify-content: space-evenly;
}
@ -7438,7 +7488,9 @@ test('justify-items', async () => {
await run([
'justify-items-start',
'justify-items-end',
'justify-items-end-safe',
'justify-items-center',
'justify-items-center-safe',
'justify-items-stretch',
]),
).toMatchInlineSnapshot(`
@ -7446,10 +7498,18 @@ test('justify-items', async () => {
justify-items: center;
}
.justify-items-center-safe {
justify-items: safe center;
}
.justify-items-end {
justify-items: end;
}
.justify-items-end-safe {
justify-items: safe end;
}
.justify-items-start {
justify-items: start;
}
@ -8298,7 +8358,9 @@ test('place-self', async () => {
'place-self-auto',
'place-self-start',
'place-self-end',
'place-self-end-safe',
'place-self-center',
'place-self-center-safe',
'place-self-stretch',
]),
).toMatchInlineSnapshot(`
@ -8310,10 +8372,18 @@ test('place-self', async () => {
place-self: center;
}
.place-self-center-safe {
place-self: safe center;
}
.place-self-end {
place-self: end;
}
.place-self-end-safe {
place-self: safe end;
}
.place-self-start {
place-self: start;
}
@ -8345,7 +8415,9 @@ test('self', async () => {
'self-auto',
'self-start',
'self-end',
'self-end-safe',
'self-center',
'self-center-safe',
'self-stretch',
'self-baseline',
]),
@ -8362,10 +8434,18 @@ test('self', async () => {
align-self: center;
}
.self-center-safe {
align-self: safe center;
}
.self-end {
align-self: flex-end;
}
.self-end-safe {
align-self: safe flex-end;
}
.self-start {
align-self: flex-start;
}
@ -8399,7 +8479,9 @@ test('justify-self', async () => {
'justify-self-auto',
'justify-self-start',
'justify-self-end',
'justify-self-end-safe',
'justify-self-center',
'justify-self-center-safe',
'justify-self-stretch',
'justify-self-baseline',
]),
@ -8412,10 +8494,18 @@ test('justify-self', async () => {
justify-self: center;
}
.justify-self-center-safe {
justify-self: safe center;
}
.justify-self-end {
justify-self: flex-end;
}
.justify-self-end-safe {
justify-self: safe flex-end;
}
.justify-self-start {
justify-self: flex-start;
}

View file

@ -11,7 +11,7 @@ import {
} from './ast'
import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate'
import type { DesignSystem } from './design-system'
import { enableBaselineLast, enableWrapAnywhere } from './feature-flags'
import { enableBaselineLast, enableSafeAlignment, enableWrapAnywhere } from './feature-flags'
import type { Theme, ThemeKey } from './theme'
import { compareBreakpoints } from './utils/compare-breakpoints'
import { DefaultMap } from './utils/default-map'
@ -1856,6 +1856,10 @@ export function createUtilities(theme: Theme) {
staticUtility('place-content-center', [['place-content', 'center']])
staticUtility('place-content-start', [['place-content', 'start']])
staticUtility('place-content-end', [['place-content', 'end']])
if (enableSafeAlignment) {
staticUtility('place-content-center-safe', [['place-content', 'safe center']])
staticUtility('place-content-end-safe', [['place-content', 'safe end']])
}
staticUtility('place-content-between', [['place-content', 'space-between']])
staticUtility('place-content-around', [['place-content', 'space-around']])
staticUtility('place-content-evenly', [['place-content', 'space-evenly']])
@ -1865,6 +1869,10 @@ export function createUtilities(theme: Theme) {
staticUtility('place-items-center', [['place-items', 'center']])
staticUtility('place-items-start', [['place-items', 'start']])
staticUtility('place-items-end', [['place-items', 'end']])
if (enableSafeAlignment) {
staticUtility('place-items-center-safe', [['place-items', 'safe center']])
staticUtility('place-items-end-safe', [['place-items', 'safe end']])
}
staticUtility('place-items-baseline', [['place-items', 'baseline']])
staticUtility('place-items-stretch', [['place-items', 'stretch']])
@ -1872,6 +1880,10 @@ export function createUtilities(theme: Theme) {
staticUtility('content-center', [['align-content', 'center']])
staticUtility('content-start', [['align-content', 'flex-start']])
staticUtility('content-end', [['align-content', 'flex-end']])
if (enableSafeAlignment) {
staticUtility('content-center-safe', [['align-content', 'safe center']])
staticUtility('content-end-safe', [['align-content', 'safe flex-end']])
}
staticUtility('content-between', [['align-content', 'space-between']])
staticUtility('content-around', [['align-content', 'space-around']])
staticUtility('content-evenly', [['align-content', 'space-evenly']])
@ -1881,6 +1893,10 @@ export function createUtilities(theme: Theme) {
staticUtility('items-center', [['align-items', 'center']])
staticUtility('items-start', [['align-items', 'flex-start']])
staticUtility('items-end', [['align-items', 'flex-end']])
if (enableSafeAlignment) {
staticUtility('items-center-safe', [['align-items', 'safe center']])
staticUtility('items-end-safe', [['align-items', 'safe flex-end']])
}
staticUtility('items-baseline', [['align-items', 'baseline']])
if (enableBaselineLast) {
staticUtility('items-baseline-last', [['align-items', 'last baseline']])
@ -1891,6 +1907,10 @@ export function createUtilities(theme: Theme) {
staticUtility('justify-center', [['justify-content', 'center']])
staticUtility('justify-start', [['justify-content', 'flex-start']])
staticUtility('justify-end', [['justify-content', 'flex-end']])
if (enableSafeAlignment) {
staticUtility('justify-center-safe', [['justify-content', 'safe center']])
staticUtility('justify-end-safe', [['justify-content', 'safe flex-end']])
}
staticUtility('justify-between', [['justify-content', 'space-between']])
staticUtility('justify-around', [['justify-content', 'space-around']])
staticUtility('justify-evenly', [['justify-content', 'space-evenly']])
@ -1901,6 +1921,10 @@ export function createUtilities(theme: Theme) {
staticUtility('justify-items-center', [['justify-items', 'center']])
staticUtility('justify-items-start', [['justify-items', 'start']])
staticUtility('justify-items-end', [['justify-items', 'end']])
if (enableSafeAlignment) {
staticUtility('justify-items-center-safe', [['justify-items', 'safe center']])
staticUtility('justify-items-end-safe', [['justify-items', 'safe end']])
}
staticUtility('justify-items-stretch', [['justify-items', 'stretch']])
spacingUtility('gap', ['--gap', '--spacing'], (value) => [decl('gap', value)])
@ -1981,12 +2005,20 @@ export function createUtilities(theme: Theme) {
staticUtility('place-self-start', [['place-self', 'start']])
staticUtility('place-self-end', [['place-self', 'end']])
staticUtility('place-self-center', [['place-self', 'center']])
if (enableSafeAlignment) {
staticUtility('place-self-end-safe', [['place-self', 'safe end']])
staticUtility('place-self-center-safe', [['place-self', 'safe center']])
}
staticUtility('place-self-stretch', [['place-self', 'stretch']])
staticUtility('self-auto', [['align-self', 'auto']])
staticUtility('self-start', [['align-self', 'flex-start']])
staticUtility('self-end', [['align-self', 'flex-end']])
staticUtility('self-center', [['align-self', 'center']])
if (enableSafeAlignment) {
staticUtility('self-end-safe', [['align-self', 'safe flex-end']])
staticUtility('self-center-safe', [['align-self', 'safe center']])
}
staticUtility('self-stretch', [['align-self', 'stretch']])
staticUtility('self-baseline', [['align-self', 'baseline']])
@ -1994,6 +2026,10 @@ export function createUtilities(theme: Theme) {
staticUtility('justify-self-start', [['justify-self', 'flex-start']])
staticUtility('justify-self-end', [['justify-self', 'flex-end']])
staticUtility('justify-self-center', [['justify-self', 'center']])
if (enableSafeAlignment) {
staticUtility('justify-self-end-safe', [['justify-self', 'safe flex-end']])
staticUtility('justify-self-center-safe', [['justify-self', 'safe center']])
}
staticUtility('justify-self-stretch', [['justify-self', 'stretch']])
for (let value of ['auto', 'hidden', 'clip', 'visible', 'scroll']) {