Spoiler alert: I'm author of Grains.js. If you missed my previous article on Grains.js, please check it out here.

Now I want to show how to use g-class directive for switching classes dynamically based on state. If you're building interactive UIs with plain HTML and want smooth transitions, animations, or dynamic styling — without using a full JS framework — then you'll love how g-class directive works in Grains.js.

Let’s dive into a real-world example.

Note: It's best to utilize TailwindCSS to use ready-made styles via their classes. g-class directive has nothing to do with TailwindCSS, however. It only switches class names based on state. After that, you can use whatever you want.

✨ Animated Notification Example

We’ll build a notification box with:

  • Visibility toggle
  • Error/success state styles
  • Shake animation for emphasis

You can try the live demo here.

You can see updated list of examples at https://github.com/mk0y/grains.js/tree/main/examples.

✅ Setup

Add Grains.js and TailwindCSS to your page:

<span class="na">defer src="https://mk0y.github.io/grains.js/dist/grains.min.js">
<span class="na">src="https://cdn.tailwindcss.com">

Remember, using cdn.tailwindcss.com is not for production environments. I'm using it only for presentational purposes - you should build your css file instead.

Next, add a simple @keyframes animation for shake:

@keyframes shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-5px); }
    75% { transform: translateX(5px); }
  }
  .shake {
    animation: shake 0.5s ease-in-out;
  }

🧠 The Markup

g-state="notifications"
  g-init='{"isVisible": false, "isError": false, "isShaking": false}'
>
  
    g-class="[
      'transform transition-all duration-300 ease-in-out',
      'p-4 rounded-lg shadow-lg mb-4',
      isVisible && 'translate-y-0 opacity-100',
      !isVisible && 'translate-y-4 opacity-0',
      isError && 'bg-red-100 text-red-700',
      !isError && 'bg-green-100 text-green-700',
      isShaking && 'shake'
    ]"
  >
     class="font-medium">Notification Message
  

   class="space-x-2">
     g-on:click="toggleVisibility">Toggle Visibility
     g-on:click="toggleError">Toggle Error State
     g-on:click="triggerShake">Shake Animation

⚙️ State Changers

Grains.js uses global pure functions to update state. Here’s how we control the behavior:

window.toggleVisibility = (ctx) => {
  ctx.set({ isVisible: !ctx.get("isVisible") });
};

window.toggleError = (ctx) => {
  ctx.set({ isError: !ctx.get("isError") });
};

window.triggerShake = (ctx) => {
  ctx.set({ isShaking: true });
  setTimeout(() => ctx.set({ isShaking: false }), 500);
};

💡 How g-class Works

  • You pass an array of strings or expressions.
  • Only truthy values are included in the final class list.
  • This makes toggling animations, colors, and layout transitions declarative and simple.

In the example above, when isVisible is true, classes like translate-y-0 and opacity-100 are added. When false, it falls back to a faded, translated state — giving a nice slide/fade transition.

🏁 Wrap-Up

The g-class directive in Grains.js makes conditional styling effortless, and when combined with Tailwind and a few keyframes, you can build delightful UI transitions in plain HTML — no frameworks or build steps required.

😊 Please let me know what you'd like to change or add as a next feature in the comments.

Thank you!