Why Tailwind ?

Comparing what we’re doing at the moment, which is using Bootstrap-fashioned self-maintained SCSS library, using the utility based Tailwind CSS library has a lots of benefits:

① Consistent utility tokens

Tailwind out of the box gives you almost every single CSS property out there but in classnames, and it is consistent, declared automatically by Tailwind. If you would declare those utility classes yourself, it would be harder to guarantee that you use the same naming convention throughout (e.g. h-100 OR h100) and you would probably have to come up with your own documentation for these collection of classes of yours.

2026-09-18T205721

Tailwind is also very flexible, if you need to define your own utility class, for instance,

  • mt-2 being 20px instead of 16px (using --spacer:10px)
  • declare new color utility classes such as bg-anu-800 / color-anu-800

Tailwind offers you a systematical way of defining/configuring those:

Basically your design system will be a part of the configuration that automatically gets applied globally, rather than a convention you will have to force the developer to follow (for instance , mt-5 is automatically margin-top of 5 * var(--spacer))

2026-09-18T210057

② More Modular Codebase

This can be divided into two parts: more coherent codebase, and less coupling between components

2.1 More coherent codebase

Declaration and usage of style happens in the same place; Comparing to the original: declare styles/classnames in SCSS/CSS, consume styles/classname in HTML, in Tailwind styleis local to the component; For instance, this is a card component example: https://flowbite.com/docs/components/card/.

2.2 Less coupling between components

Since except for the utility classnames, there is no shared classes between component (e.g. anu-card-container), each components’ styles are independent of one another; Which means changing one of the component will have no side effect to the other component.

③ No conflict with historical library

To prevent conflict with existing styles in the website, you can generate all Tailwind utility classnames with prefix without having to manually adding prefixes yourself, simply just add one line the configuration file: @import "tailwindcss" prefix(anu-una); to avoid conflict with existing classnames already in function (e.g. drop-shadow-xs now becomes anu-una-drop-shadow-xs)

2026-09-18T204347

https://tailwindcss.com/docs/upgrade-guide#using-a-prefix

Moreover, we could potentially declare tailwind css in a different layer than the original CSS

④ Flexible responsive and grid-layout

4.1 responsiveness

Instead of declaring every single responsive classnames (e.g. col-lg-4, br-lg-20, mt-lg- ←you need to define these yourself !)

2026-09-18T204549

In Tailwind, every utility class can be applied conditionally at different breakpoint without any additional setup (e.g. lg:col-4, md:rounded-xs , max-sm:mt-2 ← tailwind will automatically generate responsive classnames for you)

https://tailwindcss.com/docs/responsive-design

4.2 grid-layout

Instead of the fixed 12-columns grid in Bootstrap, you can define your own number of columns:

  • parent: <div class="grid grid-cols-10 gap-4 ...">
  • child: <div class="col-span-7"> , <div class=”col-span-3”>

(But of course you can use grid-cols-12 which is equivalant to Bootstrap’s number of columns )

https://tailwindcss.com/docs/grid-column

⑤ Export from Figma designs

You can export from Figma design to HTML code (with Tailwind classes) with a click of a button to get a baseline functional component (and the best part is that even the design tokens like primary-700 are passed along):

2026-09-18T200627

⑥ Abundant component libraries

There’s a bunch of component libraries based on Tailwind, many of them have even became industry standard or the phenomenal base library for other projects, for instance:

(*and much much more, I can confidently say there’re more than hundreds of component libraries using Tailwind)

Thought we might not be completely taking a any component library onboard, they can be good reference for our coding standards; And since so many libraries existed based on Tailwind, the popular AI models have got bunch of training data relating to Tailwind, and hence, if you ask Agentic Tools of any model to write front-end code with Tailwind, it will generally produce better outcome comparing to if you would let them to write the same thing using other less popular libraries.

⑦ Always flexible for variants

If you write a component using Tailwind, then you can always override its styles using the utility classes with important modifier (e.g. !mt-2); Which means if required, you can always add more classnames in the original component HTML. Comparing to having to declare more styles in CSS, then remove/modify classnames in HTML, this ensures there’s always a single source of truth for components, and variants are only modified version of that; Once the original copy gets modified then its variants should change too.

This also discourage the giant-sass-codebase problem, since everything can be overridden using utility token, you won’t need to have layers of CSS’s to modify the look of the component depending on which variant it is or where it is located at (e.g. _base.css → _card.scss → _card_teaser.scss → _campaign_landing.scss), everything can be in HTML

⑧ AI Support

Agentic setup: https://storybook.js.org/docs/ai/setup

MCP server: https://storybook.js.org/docs/ai/mcp/overview

🙋🏻 Yes …. But … (The Caveats)

What if they find out we’re using Tailwind, and start writing their own component using Tailwind, without following our branding guideline?

Well, this is a forever going question, if they decide to choose to deviate from our branding guideline, they always can, no matter which tech-stack we use !!!

  • For instance, even if as of now we use bootstrap-liked CSS library, they can still create and import additional global-override.css to override our existing styles that fits the branding guidelines:

    E.G. we have .anu-button { /* background-copper, text-white, gold-ring-when-focus */ } in our library, but they don’t want to use this style of button, they can always have .anu-button { /* background-red, text-green, ... */ }.

  • Even if we use Drupal SDC component, they can find out what selector we are using (e.g. div[data-sdc-component="card"] > ... > ... > .anu-button), and find way to add more specific selector to override our styling (e.g. html.content--campaign > ... > div[data-sdc-component="card"] > ... > ... > .anu-button)

So if an individual/agency/division is happy to comply and use our component (whether it is code snippet, react/vue component, web component, single-directory-component (SDC) for drupal), they will, and we should make sure it is easy and convinient for everyone (including any third-party agencies on different CMS)

For those who is not a big fan of the super long classname such as?

Some might argue that the HTML written using tailwind is hard to read, because every single component will have a handful of utility classes stacked in class property, and it will become even harder to read after you added the responsiveness and accessibility related classnames such as max-lg:max-w-50 and focus:ring-2;

Below is an example:

1
2
3
4
5
6
7
<button 
  class="relative inline-flex items-center justify-center px-6 py-3 text-sm font-medium tracking-wide text-white transition-all duration-200 ease-in-out bg-indigo-600 border border-transparent rounded-lg shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-4 focus:ring-indigo-500/50 focus:ring-offset-2 active:bg-indigo-800 disabled:opacity-60 disabled:cursor-not-allowed sm:text-base sm:px-8 sm:py-4 md:w-auto dark:bg-indigo-500 dark:hover:bg-indigo-600 dark:focus:ring-indigo-400/50 dark:focus:ring-offset-slate-900"
  type="button" aria-label="Submit payment and process order"
>
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 mr-2 -ml-1 text-indigo-200 animate-spin"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
  Process Order
</button>

(https://play.tailwindcss.com/9sOcRbn1Sk)

This example is easy to solve, you can use @apply to inline any existing utility classes into your own custom CSS:

1
2
3
4
5
6
7
8
9
@import "tailwindcss";

@theme {
  /* ... */
}

.anu-button-example{
    @apply relative inline-flex items-center justify-center px-6 py-3 text-sm font-medium tracking-wide text-white transition-all duration-200 ease-in-out bg-indigo-600 border border-transparent rounded-lg shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-4 focus:ring-indigo-500/50 focus:ring-offset-2 active:bg-indigo-800 disabled:opacity-60 disabled:cursor-not-allowed sm:text-base sm:px-8 sm:py-4 md:w-auto dark:bg-indigo-500 dark:hover:bg-indigo-600 dark:focus:ring-indigo-400/50 dark:focus:ring-offset-slate-900
}
1
2
3
4
5
6
7
<button 
  class="anu-button-example"
  type="button" aria-label="Submit payment and process order"
>
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 mr-2 -ml-1 text-indigo-200 animate-spin"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
  Process Order
</button>

(https://play.tailwindcss.com/4PlS0XWoCc)

But if you have control over how the HTML is written (e.g. you have your own SDC component in twig) then maybe you should use something other than the class property (to differentiate from the utility class we can set on DOM) such as a *-component-class property (This also helps preventing your components’s custom CSS from leaking to the other portion of the page):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@import "tailwindcss";

@theme {
  /* ... */
}
@layer components{
    [anu-component-class="button"]{
        @apply relative inline-flex items-center justify-center px-6 py-3 text-sm font-medium tracking-wide text-white transition-all duration-200 ease-in-out bg-indigo-600 border border-transparent rounded-lg shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-4 focus:ring-indigo-500/50 focus:ring-offset-2 active:bg-indigo-800 disabled:opacity-60 disabled:cursor-not-allowed sm:text-base sm:px-8 sm:py-4 md:w-auto dark:bg-indigo-500 dark:hover:bg-indigo-600 dark:focus:ring-indigo-400/50 dark:focus:ring-offset-slate-900
    }
}
1
2
3
4
5
6
7
<button 
  anu-component-class="button"
  type="button" aria-label="Submit payment and process order"
>
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 mr-2 -ml-1 text-indigo-200 animate-spin"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
  Process Order
</button>

(https://play.tailwindcss.com/8BPQk8ovmm)

With the above approach, you can also take advantage of the CSS layering in tailwind and override the default style using class property in <button> component (layer in tailwind is: ➡️ 0: properties ➡️ 1: theme ➡️ 2: base ➡️ 3: components ➡️ 4: utilities):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@import "tailwindcss";

@theme {
  /* ... */
}
@layer components{
    [anu-component-class="button"]{
        @apply relative inline-flex items-center justify-center px-6 py-3 text-sm font-medium tracking-wide text-white transition-all duration-200 ease-in-out bg-indigo-600 border border-transparent rounded-lg shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-4 focus:ring-indigo-500/50 focus:ring-offset-2 active:bg-indigo-800 disabled:opacity-60 disabled:cursor-not-allowed sm:text-base sm:px-8 sm:py-4 md:w-auto dark:bg-indigo-500 dark:hover:bg-indigo-600 dark:focus:ring-indigo-400/50 dark:focus:ring-offset-slate-900
    }
}
1
2
3
4
5
6
7
<button 
  anu-component-class="button" class="border-4 border-red-400 hover:border-green-400"
  type="button" aria-label="Submit payment and process order"
>
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" class="w-5 h-5 mr-2 -ml-1 text-indigo-200 animate-spin"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
  Process Order
</button>

(https://play.tailwindcss.com/65H6SbPMIw)


Why Storybook ?

① What it is ? and What Problem does it solve ?

(Basic Incentives)

2026-09-26T154107

Problem:

  • we want to capture the reusable visual patterns inside the website,
  • and reuse them in a more rigid, modular, maintainable way (not hard-coded in HTML)
  • which is why we’re building components
  • but render these component in an actual website takes a lot of time and effort for a developer to setup, and may involve business logic (which means there can be edge condition that you don’t see)
  • and testing can be hard sometimes cause the testing tools are usually dealing with the whole page not just any single component, and lots of back-and-forth communication between UI/UX and developer may need to be involved

Solution:

  • storybook helps by being an isolated environment that lives separate from the actual website,
  • it acts as a small, development-only, component workshop to render and test component variants
    • without the interference from other components and business logic.
  • it provides an simple interface that both developer and designer can work on at the same time
  • it provides automated testing (and manual testing) for components

Reference:

② Simulated User Behaviour (play/interaction-test)

E.g. ::focus, ::visited, tab-keypress,::hover

Reference:

③ Integration with Figma

using plug-in to add Storybook’s iframe embedding in Figma (powered by Chromatic)

same does the other way around: for every component’s story/document you can add Figma link

④ Stack and Asset Independent

Stack independent: CMS (Drupal / WordPress), React, Vue, Svelte

Asset independent: font (can be stored locally), image (aspect ratio), content (card title)

⑤ Automated A11y Testing

file-98805d0c722668a89a9fa63077216c74

Base-line accessibility testing
Custom Unit/E2E testing:

⑥ Visual regression testing

2026-09-27T192126

  • Powered by Chromatic, so you can test: how small change have impacted globally

  • For instance if you have changed a padding base unit --shadow-lg (or --padding-base-spacing), you will have changed almost all the components using shadow-lg (or p*-{#n}) classnames

  • (& you can get notification when something is worth your attention via via “Web-hook” OR “Slack”)

⑦ Responsive viewport testing

2026-09-27T192219

The viewport feature allows you to adjust the dimensions of the iframe your story is rendered in. It makes it easy to develop responsive UIs.

(https://storybook.js.org/docs/essentials/viewport)

🙋🏻 What happened to existing storybook(s) ?

The one Chaz built: ==Unusable because it is static==

The one ITS built: ==Unusable because it was not actively maintained==

  • The twig stories lives inside the SDC as component-name.stories.twig file (in Drupal theme):

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    {# some/path/in/your/code/base/my-card.stories.twig #}
    {% stories my_card with { title: 'Components/Examples/Card' } %}
      {% story default with {
        name: '1. Default',
        args: { cardHeader: 'I am a header!', buttonText: 'Learn more', buttonIconType: 'power' }
      } %}
        {# Write any Twig for the "default" story. The `args` above will be #}
        {# made available as variables for the template 👇 #}
        {% embed '../examples/my-card.html.twig' with { header: cardHeader } %}
          {% block card_body %}
            <p>I am the <em>card</em> contents.</p>
            {# You can use *any* valid Twig here, including SDCs #}
            {{ include('my-theme:my-button', { text: buttonText, iconType: buttonIconType }) }}
          {% endblock %}
        {% endembed %}
      {% endstory %}
    {% endstories %}
    
  • will get compiled into json stories where Storybook application is able to comprehend (using the Storybook Drupal Module): https://git.drupalcode.org/project/storybook/-/blob/1.x/README.md#installation

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    {
      "title": "Components/Examples/Card",
      "parameters": {
        "server": {
          "url": "https://my-site.lndo.site/storybook/stories/render"
        }
      },
      "stories": [
        {
          "name": "1. Default",
          "args": {
            "cardHeader": "I am a header!",
            "buttonText": "Learn more",
            "buttonIconType": "power"
          },
          "parameters": {
            "server": {
              "id": "eyJwYXRoIjoic29tZVwvcGF0aFwvaW5cL3lvdXJcL2NvZGVcL2Jhc2VcL215LWNhcmQuc3Rvcmllcy50d2lnIiwiaWQiOiJkZWZhdWx0In0%3D"
            }
          }
        }
      ]
    }
    
  • BUT !!!!!

    • it is not in active maintenance, and not all SDC component in theme has stories

    • at September-2026, the dependencies to run Storybook was completely removed from the repo by Roy (see screenshot: 2026-09-18T214521), but Roy was kind of doing the right thing, there is on entry-point / configuration file: .storybook/main.js, and the last time the storybook was built into storybook-static was Feb 2025 ?! (see screenshot: 2026-09-18T215915)

    • Trying to rebuild the storybook stories from source also fails:

      • running lando drush storybook:generate-all-stories --force --uri=https://content.anu.edu.au (as per instructed in webstyle/components/Readme/md yields error There are no commands defined in the "storybook" namespace.
      • after running drush pm:enable storybook -y to install storybook, followed bydrush pm:list --status=enabled | grep storybook, the same drush error persists

      And after some digging around I ==suspect== that the current storybook are written based on the older version of the Drupal (potentially Drupal 10), before the sew-drupal-isfs repository was established (it started as Drupal 11), hence the API for Single-Direcoty-Component are different (and likely so does the Storybook APIs, but I need to investigate to find out)

🙋🏻 Yes …. But … (The Caveats)

The upcoming major update is under the way, what should we do ? (The current version-10’s first stable release is on Nov-2025, the next major update version-11 is expected to rollout on Nov-2026)

We cannot bear the cost of waiting until the next major release to start the experimenting, so we might just have to use version-10 for now, the benefit is that it is stable, got plenty of documentation, and add-on supporting the current version; The version-11 may be world changing (MVP, Framework Expansion, Performance), but it is likely not going to be stable on its early game, and does not impact how we use Storybook, the underlying principle will remain the same.

Reference:

Someone will have to continue to support the storybook ?

Since storybook is isolated from the actual framework, we have to maintain it separate from the main theme/library; If no active care is given for the stories, it will quickly become outdated and becomes useless. Hence it is encouraged that the team update it alongside the theme and library, ideally it should be a part of the release workflow (dev -> storybook -> prod).

Storybook only support ESM (does not support CommonJS) ?

Storybook has removed support for CommonJS in its major version-10 update, hence all your dependencies such as package has to have ESM version of the release package.


Theme Best Practice

Also it is good to bring up this blog post of mine form a while back when I build my first in-house theme based on tailwind:

█████████████████████████

Drupal Theme - Best Practice / Principal to Follow

https://blog.simon-hu.org/posts/2026/01---january/2026-01-01-drupal-theme-best-practice/

█████████████████████████

⬇️ Indicative Table of content ⬇️

DESIGN PHASE

CODE PHASE

Design Pattern & Modularity

Responsive & Accessibility

Configuration

Miscellaneous

█████████████████████████


Reference

Speech / Podcast / Tutorials

  • YouTube - Syntax - Storybook Has Evolved w/ Jeppe Reinhold

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
     1. Introduction to Jeppe Reinhold and Storybook [00:00]
     2. What is Storybook and its three pillars (Development, Documentation, Testing) [01:43]
     3. Transition to Vite and modern build tool integrations [05:07]
     4. Reducing bundle size, dependencies, and "bloat" in Storybook 9 [07:12]
     5. Codebase modernization and upgrading from older versions [11:42]
     6. Component-based testing and the new Vitest integration [17:39]
     7. Visual regression testing with Chromatic [25:20]
     8. Best practices for component design (Presentational vs. Smart components) [29:57]
     9. Automated accessibility testing with Axe core [35:14]
    10. Useful Storybook add-ons (Record/replay interactions, CSS state forces, viewports) [41:19]
    11. Documentation features and TypeScript prop inference [45:09]
    12. Sick picks and shameless plugs [47:03]
    
  • YouTube - Syntax - Writing Maintainable CSS

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
     1. Welcome 
     2. Understanding CSS Manageability
     3. This CSS doesn’t leak to other parts of website. (and same does the other way around, it is not dependent on the parent)
     4. This CSS is easy to maintain.
     5. This CSS is reusable.
     6. Global Solutions Instead of Local Solutions.
     7. Flexibility and Adaptability in CSS
     8. Fluid Typography and Responsive Design
     9. Variables and Consistency in CSS
    10. Brought to you by Sentry.io.
    11. Values vs Variables.
    12. Choosing the Right CSS Methodology
    13. Utility CSS and Atomic CSS
    14. Exploring StyleX and Other Approaches
    15. Panda CSS.
    16. Component Scoped CSS: A Preferred Approach
    17. The Evolution of CSS Modules
    18. Global CSS: Best Practices
    19. Harnessing CSS Variables
    20. Understanding CSS Layers
    21. The Power of CSS Scoping
    22. Enforcing CSS Standards with Tools
    
  • YouTube - Figma - How to create pixel perfect UIs with StoryBook

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    1. Introduction & The Iterative Loop [01:58]
    -   Overview: Explains why strict, linear handoffs (Design $\rightarrow$ Code) fail and why UI development is an iterative loop requiring collaboration.
    -   What it means for UI/UX: Designers must design systems that can evolve rather than expecting static files to translate into production without back-and-forth adjustments.
    
    2. Managing Complexity with Variants & Stories [05:21]
    -   Overview: Compares Figma component variants ("sticker sheets") with Storybook stories, which isolate coded components to render every state (loading, error, dark mode) outside the main app.
    -   What it means for UI/UX: Figma variants serve as the structural spec for engineering; Storybook acts as the exact code equivalent, providing a shared taxonomy and mental model between designers and developers.
    
    3. Bridging Tools with Side-by-Side Embeds [14:42]
    -   Overview: Demonstrates how embedding Figma files directly inside Storybook reduces context-switching between tools.
    -   What it means for UI/UX: Developers can inspect design intent, spacing, and typography tokens right inside their coding documentation environment without constantly jumping back into Figma.
    
    4. Collaborative UI Reviews via Chromatic [18:30]
    -   Overview: Introduces cloud-based review workflows (via Chromatic) where pull requests expose live component stories for team feedback.
    -   What it means for UI/UX: Designers are brought back into the loop before code merges, allowing them to review actual rendered UI states and pin comments directly to components instead of relying on screenshots.
    
    5. Automated Visual Regression Testing [25:26]
    -   Overview: Explains how automated tools use stories to run pixel-by-pixel comparisons against established baselines to catch accidental layout breaks.
    -   What it means for UI/UX: Protects design system integrity at scale, ensuring that updates made by developers don't unintentionally break components across the rest of the application.
    
    6. Syncing Production with Figma Dev Mode [27:27]
    -   Overview: Discusses linking Figma variants directly to Storybook stories to keep design files and live production code synchronized.
    -   What it means for UI/UX: Designers can click a component variant in Figma Dev Mode and instantly preview the live, production-ready code version, maintaining a single source of truth.
    

Tutorial

Example