diff --git a/CHANGELOG.md b/CHANGELOG.md index e44a8358a064..1b70803d42ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- _Experimental_: Add utilities for the experimental `corner-shape` property (e.g. `corner-scoop`, `corner-tl-bevel`) - _Experimental_: Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901)) ## [4.1.17] - 2025-11-06 diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index 690b1cb1f0eb..8140c27a8578 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -3779,6 +3779,96 @@ exports[`getClassList 1`] = ` "contrast-125", "contrast-150", "contrast-200", + "corner-b-bevel", + "corner-b-notch", + "corner-b-round", + "corner-b-scoop", + "corner-b-square", + "corner-b-squircle", + "corner-bevel", + "corner-bl-bevel", + "corner-bl-notch", + "corner-bl-round", + "corner-bl-scoop", + "corner-bl-square", + "corner-bl-squircle", + "corner-br-bevel", + "corner-br-notch", + "corner-br-round", + "corner-br-scoop", + "corner-br-square", + "corner-br-squircle", + "corner-e-bevel", + "corner-e-notch", + "corner-e-round", + "corner-e-scoop", + "corner-e-square", + "corner-e-squircle", + "corner-ee-bevel", + "corner-ee-notch", + "corner-ee-round", + "corner-ee-scoop", + "corner-ee-square", + "corner-ee-squircle", + "corner-es-bevel", + "corner-es-notch", + "corner-es-round", + "corner-es-scoop", + "corner-es-square", + "corner-es-squircle", + "corner-l-bevel", + "corner-l-notch", + "corner-l-round", + "corner-l-scoop", + "corner-l-square", + "corner-l-squircle", + "corner-notch", + "corner-r-bevel", + "corner-r-notch", + "corner-r-round", + "corner-r-scoop", + "corner-r-square", + "corner-r-squircle", + "corner-round", + "corner-s-bevel", + "corner-s-notch", + "corner-s-round", + "corner-s-scoop", + "corner-s-square", + "corner-s-squircle", + "corner-scoop", + "corner-se-bevel", + "corner-se-notch", + "corner-se-round", + "corner-se-scoop", + "corner-se-square", + "corner-se-squircle", + "corner-square", + "corner-squircle", + "corner-ss-bevel", + "corner-ss-notch", + "corner-ss-round", + "corner-ss-scoop", + "corner-ss-square", + "corner-ss-squircle", + "corner-t-bevel", + "corner-t-notch", + "corner-t-round", + "corner-t-scoop", + "corner-t-square", + "corner-t-squircle", + "corner-tl-bevel", + "corner-tl-notch", + "corner-tl-round", + "corner-tl-scoop", + "corner-tl-square", + "corner-tl-squircle", + "corner-tr-bevel", + "corner-tr-notch", + "corner-tr-round", + "corner-tr-scoop", + "corner-tr-square", + "corner-tr-squircle", "cursor-alias", "cursor-all-scroll", "cursor-auto", diff --git a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap index 6adc96113d46..4d0467befb71 100644 --- a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap @@ -1664,3 +1664,621 @@ exports[`border-y-* 1`] = ` initial-value: solid; }" `; + +exports[`corner-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-\\[superellipse\\(0\\.6\\)\\] { + corner-shape: superellipse(.6); +} + +.corner-bevel { + corner-shape: bevel; +} + +.corner-custom { + corner-shape: var(--corner-shape-custom); +} + +.corner-notch { + corner-shape: notch; +} + +.corner-round { + corner-shape: round; +} + +.corner-scoop { + corner-shape: scoop; +} + +.corner-square { + corner-shape: square; +} + +.corner-squircle { + corner-shape: squircle; +}" +`; + +exports[`corner-b-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-b-\\[superellipse\\(0\\.6\\)\\] { + corner-bottom-right-shape: superellipse(.6); + corner-bottom-left-shape: superellipse(.6); +} + +.corner-b-bevel { + corner-bottom-right-shape: bevel; + corner-bottom-left-shape: bevel; +} + +.corner-b-custom { + corner-bottom-right-shape: var(--corner-shape-custom); + corner-bottom-left-shape: var(--corner-shape-custom); +} + +.corner-b-notch { + corner-bottom-right-shape: notch; + corner-bottom-left-shape: notch; +} + +.corner-b-round { + corner-bottom-right-shape: round; + corner-bottom-left-shape: round; +} + +.corner-b-scoop { + corner-bottom-right-shape: scoop; + corner-bottom-left-shape: scoop; +} + +.corner-b-square { + corner-bottom-right-shape: square; + corner-bottom-left-shape: square; +} + +.corner-b-squircle { + corner-bottom-right-shape: squircle; + corner-bottom-left-shape: squircle; +}" +`; + +exports[`corner-bl-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-bl-\\[superellipse\\(0\\.6\\)\\] { + corner-bottom-left-shape: superellipse(.6); +} + +.corner-bl-bevel { + corner-bottom-left-shape: bevel; +} + +.corner-bl-custom { + corner-bottom-left-shape: var(--corner-shape-custom); +} + +.corner-bl-notch { + corner-bottom-left-shape: notch; +} + +.corner-bl-round { + corner-bottom-left-shape: round; +} + +.corner-bl-scoop { + corner-bottom-left-shape: scoop; +} + +.corner-bl-square { + corner-bottom-left-shape: square; +} + +.corner-bl-squircle { + corner-bottom-left-shape: squircle; +}" +`; + +exports[`corner-br-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-br-\\[superellipse\\(0\\.6\\)\\] { + corner-bottom-right-shape: superellipse(.6); +} + +.corner-br-bevel { + corner-bottom-right-shape: bevel; +} + +.corner-br-custom { + corner-bottom-right-shape: var(--corner-shape-custom); +} + +.corner-br-notch { + corner-bottom-right-shape: notch; +} + +.corner-br-round { + corner-bottom-right-shape: round; +} + +.corner-br-scoop { + corner-bottom-right-shape: scoop; +} + +.corner-br-square { + corner-bottom-right-shape: square; +} + +.corner-br-squircle { + corner-bottom-right-shape: squircle; +}" +`; + +exports[`corner-e-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-e-\\[superellipse\\(0\\.6\\)\\] { + corner-start-end-shape: superellipse(.6); + corner-end-end-shape: superellipse(.6); +} + +.corner-e-bevel { + corner-start-end-shape: bevel; + corner-end-end-shape: bevel; +} + +.corner-e-custom { + corner-start-end-shape: var(--corner-shape-custom); + corner-end-end-shape: var(--corner-shape-custom); +} + +.corner-e-notch { + corner-start-end-shape: notch; + corner-end-end-shape: notch; +} + +.corner-e-round { + corner-start-end-shape: round; + corner-end-end-shape: round; +} + +.corner-e-scoop { + corner-start-end-shape: scoop; + corner-end-end-shape: scoop; +} + +.corner-e-square { + corner-start-end-shape: square; + corner-end-end-shape: square; +} + +.corner-e-squircle { + corner-start-end-shape: squircle; + corner-end-end-shape: squircle; +}" +`; + +exports[`corner-ee-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-ee-\\[superellipse\\(0\\.6\\)\\] { + corner-end-end-shape: superellipse(.6); +} + +.corner-ee-bevel { + corner-end-end-shape: bevel; +} + +.corner-ee-custom { + corner-end-end-shape: var(--corner-shape-custom); +} + +.corner-ee-notch { + corner-end-end-shape: notch; +} + +.corner-ee-round { + corner-end-end-shape: round; +} + +.corner-ee-scoop { + corner-end-end-shape: scoop; +} + +.corner-ee-square { + corner-end-end-shape: square; +} + +.corner-ee-squircle { + corner-end-end-shape: squircle; +}" +`; + +exports[`corner-es-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-es-\\[superellipse\\(0\\.6\\)\\] { + corner-end-start-shape: superellipse(.6); +} + +.corner-es-bevel { + corner-end-start-shape: bevel; +} + +.corner-es-custom { + corner-end-start-shape: var(--corner-shape-custom); +} + +.corner-es-notch { + corner-end-start-shape: notch; +} + +.corner-es-round { + corner-end-start-shape: round; +} + +.corner-es-scoop { + corner-end-start-shape: scoop; +} + +.corner-es-square { + corner-end-start-shape: square; +} + +.corner-es-squircle { + corner-end-start-shape: squircle; +}" +`; + +exports[`corner-l-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-l-\\[superellipse\\(0\\.6\\)\\] { + corner-top-left-shape: superellipse(.6); + corner-bottom-left-shape: superellipse(.6); +} + +.corner-l-bevel { + corner-top-left-shape: bevel; + corner-bottom-left-shape: bevel; +} + +.corner-l-custom { + corner-top-left-shape: var(--corner-shape-custom); + corner-bottom-left-shape: var(--corner-shape-custom); +} + +.corner-l-notch { + corner-top-left-shape: notch; + corner-bottom-left-shape: notch; +} + +.corner-l-round { + corner-top-left-shape: round; + corner-bottom-left-shape: round; +} + +.corner-l-scoop { + corner-top-left-shape: scoop; + corner-bottom-left-shape: scoop; +} + +.corner-l-square { + corner-top-left-shape: square; + corner-bottom-left-shape: square; +} + +.corner-l-squircle { + corner-top-left-shape: squircle; + corner-bottom-left-shape: squircle; +}" +`; + +exports[`corner-r-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-r-\\[superellipse\\(0\\.6\\)\\] { + corner-top-right-shape: superellipse(.6); + corner-bottom-right-shape: superellipse(.6); +} + +.corner-r-bevel { + corner-top-right-shape: bevel; + corner-bottom-right-shape: bevel; +} + +.corner-r-custom { + corner-top-right-shape: var(--corner-shape-custom); + corner-bottom-right-shape: var(--corner-shape-custom); +} + +.corner-r-notch { + corner-top-right-shape: notch; + corner-bottom-right-shape: notch; +} + +.corner-r-round { + corner-top-right-shape: round; + corner-bottom-right-shape: round; +} + +.corner-r-scoop { + corner-top-right-shape: scoop; + corner-bottom-right-shape: scoop; +} + +.corner-r-square { + corner-top-right-shape: square; + corner-bottom-right-shape: square; +} + +.corner-r-squircle { + corner-top-right-shape: squircle; + corner-bottom-right-shape: squircle; +}" +`; + +exports[`corner-s-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-s-\\[superellipse\\(0\\.6\\)\\] { + corner-start-start-shape: superellipse(.6); + corner-end-start-shape: superellipse(.6); +} + +.corner-s-bevel { + corner-start-start-shape: bevel; + corner-end-start-shape: bevel; +} + +.corner-s-custom { + corner-start-start-shape: var(--corner-shape-custom); + corner-end-start-shape: var(--corner-shape-custom); +} + +.corner-s-notch { + corner-start-start-shape: notch; + corner-end-start-shape: notch; +} + +.corner-s-round { + corner-start-start-shape: round; + corner-end-start-shape: round; +} + +.corner-s-scoop { + corner-start-start-shape: scoop; + corner-end-start-shape: scoop; +} + +.corner-s-square { + corner-start-start-shape: square; + corner-end-start-shape: square; +} + +.corner-s-squircle { + corner-start-start-shape: squircle; + corner-end-start-shape: squircle; +}" +`; + +exports[`corner-se-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-se-\\[superellipse\\(0\\.6\\)\\] { + corner-start-end-shape: superellipse(.6); +} + +.corner-se-bevel { + corner-start-end-shape: bevel; +} + +.corner-se-custom { + corner-start-end-shape: var(--corner-shape-custom); +} + +.corner-se-notch { + corner-start-end-shape: notch; +} + +.corner-se-round { + corner-start-end-shape: round; +} + +.corner-se-scoop { + corner-start-end-shape: scoop; +} + +.corner-se-square { + corner-start-end-shape: square; +} + +.corner-se-squircle { + corner-start-end-shape: squircle; +}" +`; + +exports[`corner-ss-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-ss-\\[superellipse\\(0\\.6\\)\\] { + corner-start-start-shape: superellipse(.6); +} + +.corner-ss-bevel { + corner-start-start-shape: bevel; +} + +.corner-ss-custom { + corner-start-start-shape: var(--corner-shape-custom); +} + +.corner-ss-notch { + corner-start-start-shape: notch; +} + +.corner-ss-round { + corner-start-start-shape: round; +} + +.corner-ss-scoop { + corner-start-start-shape: scoop; +} + +.corner-ss-square { + corner-start-start-shape: square; +} + +.corner-ss-squircle { + corner-start-start-shape: squircle; +}" +`; + +exports[`corner-t-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-t-\\[superellipse\\(0\\.6\\)\\] { + corner-top-left-shape: superellipse(.6); + corner-top-right-shape: superellipse(.6); +} + +.corner-t-bevel { + corner-top-left-shape: bevel; + corner-top-right-shape: bevel; +} + +.corner-t-custom { + corner-top-left-shape: var(--corner-shape-custom); + corner-top-right-shape: var(--corner-shape-custom); +} + +.corner-t-notch { + corner-top-left-shape: notch; + corner-top-right-shape: notch; +} + +.corner-t-round { + corner-top-left-shape: round; + corner-top-right-shape: round; +} + +.corner-t-scoop { + corner-top-left-shape: scoop; + corner-top-right-shape: scoop; +} + +.corner-t-square { + corner-top-left-shape: square; + corner-top-right-shape: square; +} + +.corner-t-squircle { + corner-top-left-shape: squircle; + corner-top-right-shape: squircle; +}" +`; + +exports[`corner-tl-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-tl-\\[superellipse\\(0\\.6\\)\\] { + corner-top-left-shape: superellipse(.6); +} + +.corner-tl-bevel { + corner-top-left-shape: bevel; +} + +.corner-tl-custom { + corner-top-left-shape: var(--corner-shape-custom); +} + +.corner-tl-notch { + corner-top-left-shape: notch; +} + +.corner-tl-round { + corner-top-left-shape: round; +} + +.corner-tl-scoop { + corner-top-left-shape: scoop; +} + +.corner-tl-square { + corner-top-left-shape: square; +} + +.corner-tl-squircle { + corner-top-left-shape: squircle; +}" +`; + +exports[`corner-tr-* 1`] = ` +":root, :host { + --corner-shape-custom: superellipse(.333); +} + +.corner-tr-\\[superellipse\\(0\\.6\\)\\] { + corner-top-right-shape: superellipse(.6); +} + +.corner-tr-bevel { + corner-top-right-shape: bevel; +} + +.corner-tr-custom { + corner-top-right-shape: var(--corner-shape-custom); +} + +.corner-tr-notch { + corner-top-right-shape: notch; +} + +.corner-tr-round { + corner-top-right-shape: round; +} + +.corner-tr-scoop { + corner-top-right-shape: scoop; +} + +.corner-tr-square { + corner-top-right-shape: square; +} + +.corner-tr-squircle { + corner-top-right-shape: squircle; +}" +`; diff --git a/packages/tailwindcss/src/feature-flags.ts b/packages/tailwindcss/src/feature-flags.ts index 5a262056c78c..9c2d2d148f60 100644 --- a/packages/tailwindcss/src/feature-flags.ts +++ b/packages/tailwindcss/src/feature-flags.ts @@ -1 +1,2 @@ export const enableContainerSizeUtility = process.env.FEATURES_ENV !== 'stable' +export const enableCornerShapeUtilities = process.env.FEATURES_ENV !== 'stable' diff --git a/packages/tailwindcss/src/property-order.ts b/packages/tailwindcss/src/property-order.ts index f024f69771a0..44a4225a1ed6 100644 --- a/packages/tailwindcss/src/property-order.ts +++ b/packages/tailwindcss/src/property-order.ts @@ -191,6 +191,16 @@ export default [ 'border-bottom-right-radius', 'border-bottom-left-radius', + 'corner-shape', + 'corner-start-start-shape', + 'corner-start-end-shape', + 'corner-end-end-shape', + 'corner-end-start-shape', + 'corner-top-left-shape', + 'corner-top-right-shape', + 'corner-bottom-right-shape', + 'corner-bottom-left-shape', + 'border-width', 'border-inline-width', 'border-block-width', diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 233d3fa23bce..85c633b8103a 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -11051,6 +11051,66 @@ test('rounded-bl', async () => { ).toEqual('') }) +// All corner utilities are generated in the same way +// so we can test them all at once with a loop +const cornerPrefixes = [ + 'corner', + 'corner-s', + 'corner-e', + 'corner-t', + 'corner-r', + 'corner-b', + 'corner-l', + 'corner-ss', + 'corner-se', + 'corner-ee', + 'corner-es', + 'corner-tl', + 'corner-tr', + 'corner-br', + 'corner-bl', +] + +for (let prefix of cornerPrefixes) { + test(`${prefix}-*`, async () => { + let classes = [] + + // Corner shape + classes.push(`${prefix}-round`) + classes.push(`${prefix}-scoop`) + classes.push(`${prefix}-bevel`) + classes.push(`${prefix}-notch`) + classes.push(`${prefix}-square`) + classes.push(`${prefix}-squircle`) + classes.push(`${prefix}-custom`) + classes.push(`${prefix}-[superellipse(0.6)]`) + + expect( + await compileCss( + css` + @theme { + --corner-shape-custom: superellipse(0.333); + } + @tailwind utilities; + `, + classes, + ), + ).toMatchSnapshot() + + expect( + await run( + classes.flatMap((cls) => [ + // No corner utilities can ever be negative + `-${cls}`, + + // Nor can they have modifiers + `${cls}/foo`, + ]), + ), + ).toEqual('') + }) +} + test('border-style', async () => { expect( await run([ diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 521cf974d9d6..2bf65f97d40b 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -12,7 +12,7 @@ import { } from './ast' import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate' import type { DesignSystem } from './design-system' -import { enableContainerSizeUtility } from './feature-flags' +import { enableContainerSizeUtility, enableCornerShapeUtilities } from './feature-flags' import type { Theme, ThemeKey } from './theme' import { compareBreakpoints } from './utils/compare-breakpoints' import { DefaultMap } from './utils/default-map' @@ -2199,6 +2199,37 @@ export function createUtilities(theme: Theme) { } } + if (enableCornerShapeUtilities) { + // corner-shape + let shapes = ['round', 'scoop', 'bevel', 'notch', 'square', 'squircle'] + + for (let [root, properties] of [ + ['corner', ['corner-shape']], + ['corner-s', ['corner-start-start-shape', 'corner-end-start-shape']], + ['corner-e', ['corner-start-end-shape', 'corner-end-end-shape']], + ['corner-t', ['corner-top-left-shape', 'corner-top-right-shape']], + ['corner-r', ['corner-top-right-shape', 'corner-bottom-right-shape']], + ['corner-b', ['corner-bottom-right-shape', 'corner-bottom-left-shape']], + ['corner-l', ['corner-top-left-shape', 'corner-bottom-left-shape']], + ['corner-ss', ['corner-start-start-shape']], + ['corner-se', ['corner-start-end-shape']], + ['corner-ee', ['corner-end-end-shape']], + ['corner-es', ['corner-end-start-shape']], + ['corner-tl', ['corner-top-left-shape']], + ['corner-tr', ['corner-top-right-shape']], + ['corner-br', ['corner-bottom-right-shape']], + ['corner-bl', ['corner-bottom-left-shape']], + ] as const) { + functionalUtility(root, { + themeKeys: ['--corner-shape'], + handle: (value) => properties.map((property) => decl(property, value)), + staticValues: Object.fromEntries( + shapes.map((shape) => [shape, properties.map((property) => decl(property, shape))]), + ), + }) + } + } + staticUtility('border-solid', [ ['--tw-border-style', 'solid'], ['border-style', 'solid'],