Skip to content

Instantly share code, notes, and snippets.

@davidwebca
Last active December 9, 2021 05:15
Show Gist options
  • Save davidwebca/06899eb675b94922f588dd7cd7b3b9c3 to your computer and use it in GitHub Desktop.
Save davidwebca/06899eb675b94922f588dd7cd7b3b9c3 to your computer and use it in GitHub Desktop.
Tailwind JIT Dynamic Scoping

Tailwind Dynamic Scoping

This is a quick and dirty simple method to allow dynamic scoping in Tailwind CSS. It happens on-the-fly thanks to the JIT engine and doesn't need any new config when you want to add a new scope.

Generate this

.admin .scope-\[\.admin\|bg-red-500\+underline\] {
  --tw-bg-opacity: 1;
  background-color: rgb(239 68 68 / var(--tw-bg-opacity));
  text-decoration: underline;
}

with this

<div class="scope-[.admin|bg-red-500+underline]"></div>

Why would you use that?

Well, in some platforms like WordPress, with the new editor Gutenberg, we are encouraged to re-use the markup in the frontend and backend for the dynamic live preview. To keep our views as DRY as possible, we shouldn't need to add logic to conditionally attach different CSS classes in the admin or the frontend.

Another use case is to customize some styles depending on their parent. For example, your card component should have a different text size when it's included in an accordion, but not when it's used in a blog list. That way, your card component stays aware of its possible contexts without the need to create specific overriding classes.

Since it's so simple (barely a few lines in tailwind.config.js), it doesn't really deserve its own Tailwind plugin package and will probably make my tailwindcss-group-variants obsolete.

See it in action here: https://play.tailwindcss.com/7ZR7xajPor

Basic Tailwind 2.1+ config

module.exports = {
  mode: 'jit',
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [
    /* Tailwind JIT Dynamic Scoping - Add this part to your plugins array */
    ({ matchUtilities }) => {
      matchUtilities({
        scope: (value) => {
          // We need an arbitrary split character to receive the selector
          let split = value.split('|')
          // Spaces aren't allowed by JIT's regexes, we use + instead
          let rules = split[1].split('+')
          return {
            [`${split[0]} &`]: {
              // JIT engine compiles @apply rules dynamically
              [`@apply ${rules.join(' ')}`]: '',
            },
          }
        },
      })
    }
    /* End Tailwind JIT Dynamic Scoping */
  ],
}

Example HTML

<div class="frontend">
  <div class="bg-purple-900 scope-[.admin|bg-red-500+underline] p-12 m-12">
    <div class="text-white">
      This card has a purple background in the frontend.
    </div>
  </div>
</div>
<div class="admin">
  <div class="bg-purple-900 scope-[.admin|bg-red-500+underline] p-12 m-12">
    <div class="text-white">
      This card has a red background and underlined text in the backend even if they both have the same markup.
    </div>
  </div>
</div>

Resulting CSS (without CSS reset)

.m-12 {
  margin: 3rem;
}

.bg-purple-900 {
  --tw-bg-opacity: 1;
  background-color: rgb(88 28 135 / var(--tw-bg-opacity));
}

.p-12 {
  padding: 3rem;
}

.text-white {
  --tw-text-opacity: 1;
  color: rgb(255 255 255 / var(--tw-text-opacity));
}

.admin .scope-\[\.admin\|bg-red-500\+underline\] {
  --tw-bg-opacity: 1;
  background-color: rgb(239 68 68 / var(--tw-bg-opacity));
  text-decoration: underline;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment