Steve Kinney

Not Utility

This is new in Tailwind 4!

The not-* variant applies styles when a condition is NOT true, simplifying conditional styling and reducing the need for default styles that get overridden.

Basic Usage

<!-- Subtle opacity when not hovered, full opacity on hover -->
<button
  class="rounded bg-blue-500 px-4 py-2 text-white transition-opacity duration-200 not-hover:opacity-80 hover:opacity-100"
>
  Hover me
</button>

<!-- Muted border when not focused, accent border on focus -->
<input
  class="rounded-md border px-3 py-2 transition-colors not-focus:border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-100"
  placeholder="Focus to see border change"
/>

Common Patterns

Interactive Button States

<!-- Button that's interactive when enabled, muted when disabled -->
<button
  class="rounded-lg bg-blue-500 px-6 py-3 font-medium text-white transition-all not-disabled:cursor-pointer not-disabled:hover:bg-blue-600 not-disabled:active:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-40"
  disabled
>
  Submit Form
</button>

Responsive Visibility

<!-- Hide on mobile when not expanded, always show on desktop -->
<nav class="transition-all not-open:hidden not-open:md:block">
  <ul class="space-y-2">
    <li><a href="#" class="block rounded p-2 hover:bg-gray-100">Home</a></li>
    <li><a href="#" class="block rounded p-2 hover:bg-gray-100">About</a></li>
  </ul>
</nav>

Advanced Compositions

Group Interactions

Interactive Card

Hover the card to see the content brighten

<!-- Card that dims its content when parent is not hovered -->
<div class="group rounded-lg bg-white p-6 shadow-md transition-shadow hover:shadow-lg">
  <h3
    class="group-not-hover:opacity-60 mb-2 text-xl font-bold transition-opacity group-hover:opacity-100"
  >
    Interactive Card
  </h3>
  <p class="group-not-hover:opacity-50 text-gray-600 transition-opacity group-hover:opacity-100">
    Hover the card to see the content brighten
  </p>
</div>

Peer-Based Styling

Notifications enabled
<!-- Toggle switch with dependent text styling -->
<div class="flex items-center space-x-3">
  <input type="checkbox" id="notifications" class="peer sr-only" />
  <label
    for="notifications"
    class="relative h-6 w-11 cursor-pointer rounded-full bg-gray-200 peer-checked:bg-blue-600 before:absolute before:top-0.5 before:left-0.5 before:h-5 before:w-5 before:rounded-full before:bg-white before:transition-transform peer-checked:before:translate-x-5"
  ></label>
  <span
    class="font-medium transition-colors peer-not-checked:text-gray-500 peer-checked:text-blue-600"
  >
    Notifications enabled
  </span>
</div>

Complex State Management

Password must be at least 8 characters long

<!-- Input with dynamic validation feedback -->
<div class="space-y-2">
  <input
    type="password"
    class="w-full rounded-md border px-3 py-2 transition-colors not-has-[:valid]:border-red-300 not-has-[:valid]:focus:ring-red-100 has-[:valid]:border-green-300 has-[:valid]:focus:ring-green-100"
    minlength="8"
    placeholder="Enter password (min 8 characters)"
  />
  <p
    class="text-sm transition-colors not-has-[input:valid]:text-red-600 has-[input:valid]:text-green-600"
  >
    Password must be at least 8 characters long
  </p>
</div>

Practical Examples

Form Field Validation

Please enter a valid email address
<!-- Email field with real-time validation -->
<div class="space-y-2">
  <label
    class="block font-medium transition-colors not-has-[:valid]:text-red-700 has-[:valid]:text-green-700"
  >
    <span class="mb-1 block">Email Address</span>
    <input
      type="email"
      required
      class="w-full rounded-md border px-3 py-2 transition-colors not-valid:border-red-300 valid:border-green-300 not-valid:focus:ring-red-100 valid:focus:ring-green-100"
      placeholder="your@email.com"
    />
  </label>
  <div class="text-sm text-red-600 not-has-[:valid]:block has-[:valid]:hidden">
    Please enter a valid email address
  </div>
</div>

Dynamic List Styling

<!-- List with smart borders and hover effects -->
<ul class="divide-y divide-gray-200 rounded-lg bg-white shadow">
  <li class="transition-colors not-last:border-b not-last:border-gray-100 hover:bg-gray-50">
    <a href="#" class="block px-6 py-4">First item</a>
  </li>
  <li class="transition-colors not-last:border-b not-last:border-gray-100 hover:bg-gray-50">
    <a href="#" class="block px-6 py-4">Second item</a>
  </li>
  <li class="transition-colors hover:bg-gray-50">
    <a href="#" class="block px-6 py-4">Last item (no border)</a>
  </li>
</ul>

Feature Detection Fallbacks

Item 1
Item 2
Item 3
Content with smart background
<!-- Grid layout with flexbox fallback -->
<div
  class="p-6 not-supports-[display:grid]:flex not-supports-[display:grid]:flex-wrap not-supports-[display:grid]:gap-4 supports-[display:grid]:grid supports-[display:grid]:grid-cols-3 supports-[display:grid]:gap-6"
>
  <div class="rounded-lg bg-blue-100 p-4">Item 1</div>
  <div class="rounded-lg bg-green-100 p-4">Item 2</div>
  <div class="rounded-lg bg-purple-100 p-4">Item 3</div>
</div>

<!-- Modern backdrop-blur with solid background fallback -->
<div
  class="rounded-lg p-6 shadow-lg not-supports-[backdrop-filter:blur(10px)]:border not-supports-[backdrop-filter:blur(10px)]:bg-white supports-[backdrop-filter:blur(10px)]:bg-white/80 supports-[backdrop-filter:blur(10px)]:backdrop-blur-lg"
>
  Content with smart background
</div>

Benefits

  • Cleaner Logic: Express negative conditions directly instead of overriding defaults
  • Better Performance: Avoid unnecessary style recalculations from overrides
  • Improved Readability: Makes conditional styling intent immediately clear
  • Universal Compatibility: Works with all Tailwind variants (hover, focus, group, peer, etc.)
  • Reduced Specificity Issues: Less reliance on cascade order for conditional styles

Last modified on .