Skip to content

Add logical inset utilities (inset-s, inset-e, inset-bs, inset-be)#19613

Merged
RobinMalfait merged 4 commits intomainfrom
claude/add-logical-inset-utilities-NXXck
Jan 30, 2026
Merged

Add logical inset utilities (inset-s, inset-e, inset-bs, inset-be)#19613
RobinMalfait merged 4 commits intomainfrom
claude/add-logical-inset-utilities-NXXck

Conversation

@adamwathan
Copy link
Member

Summary

This PR adds support for logical inset utilities that map to CSS logical properties:

  • inset-sinset-inline-start
  • inset-einset-inline-end
  • inset-bsinset-block-start
  • inset-beinset-block-end

These utilities complement the existing inset-x (inline) and inset-y (block) utilities, providing more granular control over positioning in a direction-aware manner. This aligns with CSS logical properties and improves support for internationalization (RTL/LTR languages).

Changes

  1. property-order.ts: Added inset-block-start and inset-block-end to the property ordering list to ensure consistent cascade ordering
  2. utilities.ts: Added four new utility mappings for the logical inset properties
  3. utilities.test.ts: Added comprehensive test coverage for all four new utilities, including:
    • Valid class generation with various value types (custom spacing, percentages, arbitrary values)
    • Negative value support
    • Invalid class rejection (malformed syntax, invalid modifiers)

Test plan

All changes are covered by unit tests in utilities.test.ts:

  • inset-s test: 8 valid classes + 13 invalid class assertions
  • inset-e test: 8 valid classes + 13 invalid class assertions
  • inset-bs test: 8 valid classes + 13 invalid class assertions
  • inset-be test: 8 valid classes + 13 invalid class assertions

Each test verifies correct CSS output generation and proper rejection of malformed utilities.

https://claude.ai/code/session_01JcYXVAMawRuntKatjku1WZ

@adamwathan adamwathan requested a review from a team as a code owner January 29, 2026 14:38
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

Walkthrough

The PR adds support for block-axis inset properties by inserting inset-block-start and inset-block-end into the property order, registers four new utilities (inset-s, inset-e, inset-bs, inset-be) mapped to inset-inline-start, inset-inline-end, inset-block-start, and inset-block-end, expands legacy compat handling with a new handleInset implementation for start/end tokens, adds extensive tests exercising utility generation and theme resolution, and updates the changelog with the new utilities and related size/block variants.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding four new logical inset utilities (inset-s, inset-e, inset-bs, inset-be) that map to CSS logical properties.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the purpose, listing all four new logical inset utilities, and detailing the changes across multiple files with comprehensive test coverage information.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

🧪 Unit Test Generation v2 is now available!

We have significantly improved our unit test generation capabilities.

To enable: Add this to your .coderabbit.yaml configuration:

reviews:
  finishing_touches:
    unit_tests:
      enabled: true

Try it out by using the @coderabbitai generate unit tests command on your code files or under ✨ Finishing Touches on the walkthrough!

Have feedback? Share your thoughts on our Discord thread!


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/tailwindcss/src/utilities.ts (1)

519-571: ⚠️ Potential issue | 🟡 Minor

Autocomplete suppression is incomplete for deprecated spacing utilities.

skipSuggestions only prevents the extra DEFAULT_SPACING_SUGGESTIONS, but functionalUtility still registers suggestions (theme keys + static values). That means deprecated start-* / end-* will still appear in completions. Consider plumbing skipSuggestions through functionalUtility and guarding its suggestion registration so deprecated utilities are fully hidden.

Proposed fix (guard suggestions in functionalUtility)
 type UtilityDescription = {
   supportsNegative?: boolean
   supportsFractions?: boolean
   themeKeys?: ThemeKey[]
   defaultValue?: string | null
   staticValues?: Record<string, AstNode[]>
+  skipSuggestions?: boolean
   handleBareValue?: (value: NamedUtilityValue) => string | null
   handleNegativeBareValue?: (value: NamedUtilityValue) => string | null
   handle: (value: string, dataType: string | null) => AstNode[] | undefined
 }

 function functionalUtility(classRoot: string, desc: UtilityDescription) {
   ...
-  suggest(classRoot, () => [
-    {
-      supportsNegative: desc.supportsNegative,
-      valueThemeKeys: desc.themeKeys ?? [],
-      hasDefaultValue: desc.defaultValue !== undefined && desc.defaultValue !== null,
-      supportsFractions: desc.supportsFractions,
-    },
-  ])
-
-  if (desc.staticValues && Object.keys(desc.staticValues).length > 0) {
-    let values = Object.keys(desc.staticValues)
-    suggest(classRoot, () => [{ values }])
-  }
+  if (!desc.skipSuggestions) {
+    suggest(classRoot, () => [
+      {
+        supportsNegative: desc.supportsNegative,
+        valueThemeKeys: desc.themeKeys ?? [],
+        hasDefaultValue: desc.defaultValue !== undefined && desc.defaultValue !== null,
+        supportsFractions: desc.supportsFractions,
+      },
+    ])
+
+    if (desc.staticValues && Object.keys(desc.staticValues).length > 0) {
+      let values = Object.keys(desc.staticValues)
+      suggest(classRoot, () => [{ values }])
+    }
+  }
 }

 spacingUtility(...){
   functionalUtility(name, {
     ...
     staticValues,
+    skipSuggestions,
   })
   if (!skipSuggestions) {
     suggest(name, () => [ ... ])
   }
 }

claude and others added 4 commits January 30, 2026 14:48
Add new logical property-based inset utilities:
- inset-s-* → inset-inline-start (alias for start-*)
- inset-e-* → inset-inline-end (alias for end-*)
- inset-bs-* → inset-block-start (new)
- inset-be-* → inset-block-end (new)

These utilities support the same value set as other inset utilities including
spacing scale values, arbitrary values, auto, full, and negative values.

https://claude.ai/code/session_01JcYXVAMawRuntKatjku1WZ
…inset-e-*

Add `skipSuggestions` option to `spacingUtility` to control whether
utilities appear in autocomplete suggestions. Use this to soft-deprecate
the `start-*` and `end-*` utilities - they still work but won't be
suggested in autocomplete, encouraging users to use `inset-s-*` and
`inset-e-*` instead.

https://claude.ai/code/session_01JcYXVAMawRuntKatjku1WZ
We already had legacy utilities in that file. I think I want to
eventually keep those in the utilities file directly and introduce some
deprecated flag and suggestion for the replacement.
@RobinMalfait RobinMalfait force-pushed the claude/add-logical-inset-utilities-NXXck branch from 5bd2b0f to e4c3036 Compare January 30, 2026 14:08
@RobinMalfait RobinMalfait enabled auto-merge (squash) January 30, 2026 14:10
@RobinMalfait RobinMalfait merged commit 5395c7e into main Jan 30, 2026
7 checks passed
@RobinMalfait RobinMalfait deleted the claude/add-logical-inset-utilities-NXXck branch January 30, 2026 14:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@CHANGELOG.md`:
- Around line 23-29: Update the deprecation line that currently references the
replacements for start-* and end-* to point to the correct new inset utilities:
replace the mention of inline-s-* and inline-e-* with inset-s-* and inset-e-* so
the deprecation reads that start-* and end-* are deprecated in favor of
inset-s-* and inset-e-* (refer to the symbols start-*, end-*, inset-s-*,
inset-e-* to locate and update the text).

In `@packages/tailwindcss/src/compat/legacy-utilities.ts`:
- Around line 127-140: The returned function in handleInset currently checks
candidate.modifier only in the no-value and arbitrary branches, allowing classes
like start-4/foo to slip through; add a top-level guard at the start of the
returned function (inside handleInset's returned arrow) that returns immediately
if candidate.modifier is truthy so modifiers are rejected consistently for
legacy start/end utilities (keep existing negative handling and the rest of the
branches unchanged).
- Around line 149-151: The fraction handling currently only checks
isPositiveInteger on both parts and can accept a zero denominator (e.g., "1/0");
update the block that splits with segment(candidate.value.fraction, '/') to also
detect and reject a zero denominator (check rhs !== '0' or parseInt(rhs,10) !==
0) before computing value = `calc(${candidate.value.fraction} * 100%)`; ensure
the function returns early when rhs is zero so invalid CSS is not emitted
(references: segment, candidate.value.fraction, isPositiveInteger, value).

Comment on lines +23 to +29
- Add `pbs-*`, `pbe-*`, `mbs-*`, `mbe-*`, `scroll-pbs-*`, `scroll-pbe-*`, `scroll-mbs-*`, `scroll-mbe-*`, `border-bs-*`, `border-be-*` utilities for `padding-block-start`, `padding-block-end`, `margin-block-start`, `margin-block-end`, `scroll-padding-block-start`, `scroll-padding-block-end`, `scroll-margin-block-start`, `scroll-margin-block-end`, `border-block-start`, and `border-block-end` ([`#19601`](https://github.com/tailwindlabs/tailwindcss/pull/19601))
- Add `inline-*`, `min-inline-*`, `max-inline-*`, `block-*`, `min-block-*`, `max-block-*` utilities for `inline-size`, `min-inline-size`, `max-inline-size`, `block-size`, `min-block-size`, and `max-block-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612))
- Add `inset-s-*`, `inset-e-*`, `inset-bs-*`, `inset-be-*` utilities for `inset-inline-start`, `inset-inline-end`, `inset-block-start`, and `inset-block-end` ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613))

### Deprecated

- Deprecate `start-*` and `end-*` utilities in favor of `inline-s-*` and `inline-e-*` ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Deprecation note should reference inset-s-* / inset-e-*.

Line 29 currently points to inline-s-*/inline-e-*, but the replacements for start-*/end-* are the new inset utilities.

Suggested fix
-- Deprecate `start-*` and `end-*` utilities in favor of `inline-s-*` and `inline-e-*` ([`#19613`](https://github.com/tailwindlabs/tailwindcss/pull/19613))
+- Deprecate `start-*` and `end-*` utilities in favor of `inset-s-*` and `inset-e-*` ([`#19613`](https://github.com/tailwindlabs/tailwindcss/pull/19613))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Add `pbs-*`, `pbe-*`, `mbs-*`, `mbe-*`, `scroll-pbs-*`, `scroll-pbe-*`, `scroll-mbs-*`, `scroll-mbe-*`, `border-bs-*`, `border-be-*` utilities for `padding-block-start`, `padding-block-end`, `margin-block-start`, `margin-block-end`, `scroll-padding-block-start`, `scroll-padding-block-end`, `scroll-margin-block-start`, `scroll-margin-block-end`, `border-block-start`, and `border-block-end` ([`#19601`](https://github.com/tailwindlabs/tailwindcss/pull/19601))
- Add `inline-*`, `min-inline-*`, `max-inline-*`, `block-*`, `min-block-*`, `max-block-*` utilities for `inline-size`, `min-inline-size`, `max-inline-size`, `block-size`, `min-block-size`, and `max-block-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612))
- Add `inset-s-*`, `inset-e-*`, `inset-bs-*`, `inset-be-*` utilities for `inset-inline-start`, `inset-inline-end`, `inset-block-start`, and `inset-block-end` ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613))
### Deprecated
- Deprecate `start-*` and `end-*` utilities in favor of `inline-s-*` and `inline-e-*` ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613))
- Add `pbs-*`, `pbe-*`, `mbs-*`, `mbe-*`, `scroll-pbs-*`, `scroll-pbe-*`, `scroll-mbs-*`, `scroll-mbe-*`, `border-bs-*`, `border-be-*` utilities for `padding-block-start`, `padding-block-end`, `margin-block-start`, `margin-block-end`, `scroll-padding-block-start`, `scroll-padding-block-end`, `scroll-margin-block-start`, `scroll-margin-block-end`, `border-block-start`, and `border-block-end` ([`#19601`](https://github.com/tailwindlabs/tailwindcss/pull/19601))
- Add `inline-*`, `min-inline-*`, `max-inline-*`, `block-*`, `min-block-*`, `max-block-*` utilities for `inline-size`, `min-inline-size`, `max-inline-size`, `block-size`, `min-block-size`, and `max-block-size` ([`#19612`](https://github.com/tailwindlabs/tailwindcss/pull/19612))
- Add `inset-s-*`, `inset-e-*`, `inset-bs-*`, `inset-be-*` utilities for `inset-inline-start`, `inset-inline-end`, `inset-block-start`, and `inset-block-end` ([`#19613`](https://github.com/tailwindlabs/tailwindcss/pull/19613))
### Deprecated
- Deprecate `start-*` and `end-*` utilities in favor of `inset-s-*` and `inset-e-*` ([`#19613`](https://github.com/tailwindlabs/tailwindcss/pull/19613))
🤖 Prompt for AI Agents
In `@CHANGELOG.md` around lines 23 - 29, Update the deprecation line that
currently references the replacements for start-* and end-* to point to the
correct new inset utilities: replace the mention of inline-s-* and inline-e-*
with inset-s-* and inset-e-* so the deprecation reads that start-* and end-* are
deprecated in favor of inset-s-* and inset-e-* (refer to the symbols start-*,
end-*, inset-s-*, inset-e-* to locate and update the text).

Comment on lines +127 to +140
function handleInset({ negative }: { negative: boolean }) {
return (candidate: Extract<import('../candidate').Candidate, { kind: 'functional' }>) => {
if (!candidate.value) {
if (candidate.modifier) return
let value = designSystem.theme.resolve(null, ['--inset', '--spacing'])
if (value === null) return
return [decl(property, negative ? `calc(${value} * -1)` : value)]
}

if (candidate.value.kind === 'arbitrary') {
if (candidate.modifier) return
let value = candidate.value.value
return [decl(property, negative ? `calc(${value} * -1)` : value)]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject modifiers for legacy start/end utilities.

Line 128: modifiers like start-4/foo can still generate CSS because the modifier check only exists in the no-value/arbitrary branches. A top-level guard keeps invalid classes from leaking through and matches the behavior of other functional utilities.

Suggested fix
 function handleInset({ negative }: { negative: boolean }) {
   return (candidate: Extract<import('../candidate').Candidate, { kind: 'functional' }>) => {
+    if (candidate.modifier) return
     if (!candidate.value) {
-      if (candidate.modifier) return
       let value = designSystem.theme.resolve(null, ['--inset', '--spacing'])
       if (value === null) return
       return [decl(property, negative ? `calc(${value} * -1)` : value)]
     }

     if (candidate.value.kind === 'arbitrary') {
-      if (candidate.modifier) return
       let value = candidate.value.value
       return [decl(property, negative ? `calc(${value} * -1)` : value)]
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function handleInset({ negative }: { negative: boolean }) {
return (candidate: Extract<import('../candidate').Candidate, { kind: 'functional' }>) => {
if (!candidate.value) {
if (candidate.modifier) return
let value = designSystem.theme.resolve(null, ['--inset', '--spacing'])
if (value === null) return
return [decl(property, negative ? `calc(${value} * -1)` : value)]
}
if (candidate.value.kind === 'arbitrary') {
if (candidate.modifier) return
let value = candidate.value.value
return [decl(property, negative ? `calc(${value} * -1)` : value)]
}
function handleInset({ negative }: { negative: boolean }) {
return (candidate: Extract<import('../candidate').Candidate, { kind: 'functional' }>) => {
if (candidate.modifier) return
if (!candidate.value) {
let value = designSystem.theme.resolve(null, ['--inset', '--spacing'])
if (value === null) return
return [decl(property, negative ? `calc(${value} * -1)` : value)]
}
if (candidate.value.kind === 'arbitrary') {
let value = candidate.value.value
return [decl(property, negative ? `calc(${value} * -1)` : value)]
}
🤖 Prompt for AI Agents
In `@packages/tailwindcss/src/compat/legacy-utilities.ts` around lines 127 - 140,
The returned function in handleInset currently checks candidate.modifier only in
the no-value and arbitrary branches, allowing classes like start-4/foo to slip
through; add a top-level guard at the start of the returned function (inside
handleInset's returned arrow) that returns immediately if candidate.modifier is
truthy so modifiers are rejected consistently for legacy start/end utilities
(keep existing negative handling and the rest of the branches unchanged).

Comment on lines +149 to +151
let [lhs, rhs] = segment(candidate.value.fraction, '/')
if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) return
value = `calc(${candidate.value.fraction} * 100%)`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against zero denominators in fractions.

start-1/0 currently passes isPositiveInteger and emits invalid CSS (calc(1/0 * 100%)). Add an explicit zero check.

Suggested fix
-        if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) return
+        if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs) || Number(rhs) === 0) return
🤖 Prompt for AI Agents
In `@packages/tailwindcss/src/compat/legacy-utilities.ts` around lines 149 - 151,
The fraction handling currently only checks isPositiveInteger on both parts and
can accept a zero denominator (e.g., "1/0"); update the block that splits with
segment(candidate.value.fraction, '/') to also detect and reject a zero
denominator (check rhs !== '0' or parseInt(rhs,10) !== 0) before computing value
= `calc(${candidate.value.fraction} * 100%)`; ensure the function returns early
when rhs is zero so invalid CSS is not emitted (references: segment,
candidate.value.fraction, isPositiveInteger, value).

RobinMalfait pushed a commit that referenced this pull request Feb 19, 2026
I noticed this when reading the changelog to migrate a project to the
new version. I'm not sure whether retroactively changing the changelog
is considered acceptable behaviour in this project or whether appending
a notice in the next changelog would be better.

Funnily this was actually guessed in #19613, though hard to notice among
the other many false guesses.
javierjulio added a commit to activeadmin/activeadmin that referenced this pull request Feb 19, 2026
TailwindCSS v4.2.0 changelog: Deprecate start-* and end-* utilities in favor of inset-s-* and inset-e-* utilities

tailwindlabs/tailwindcss#19613

Despite the start-0 change to inset-s-0, it seems that Flowbite will still add a left-0 on the main-menu element when reviewing the HTML in the inspector.

We only had a single instance of start-* in a template file. The other is just for our test suite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments