Tailwind UI in Svelte

Written — Updated

The Tailwind UI dynamic code is written using Alpine.js, so most of the integration effort goes into translating the Alpine.js code to Svelte.

Update: Since I wrote this the Tailwind UI site has changed to use informative comments instead of Alpine code samples. The Svelte techniques below still apply.

Conditional Showing of Elements 🔗

Alpine:

<div x-data="{open: true}">
    <div x-show="open">...</div>
</div>

Svelte:

<script>
  let open = true;
</script>

<div>
    {#if open}
        <div>...</div>
    {/if}
</div>

Conditional Classes 🔗

Alpine:

<div :class="{'block': open, 'hidden': !open}">...</div>

Svelte:

<div class:block={open} class:hidden={!open}>...</div>

Event Handlers 🔗

Button Click 🔗

Alpine:

<button @click="open = !open">...</button>

Svelte:

<button on:click={() => open = !open}>...</button>

Click Outside 🔗

Alpine:

<div @click.away="open = false">
    ...
    <div x-show={open}>...</div>
</div>

Svelte (REPL):


<script>
function clickOutside(node, { enabled: initialEnabled, cb }) {
    const handleOutsideClick = ({ target }) => {
      if (!node.contains(target)) {
        cb();
      }
    };

    function update({enabled}) {
      if (enabled) {
        window.addEventListener('click', handleOutsideClick);
      } else {
        window.removeEventListener('click', handleOutsideClick);
      }
    }

    update(initialEnabled);
    return {
      update,
      destroy() {
        window.removeEventListener( 'click', handleOutsideClick );
      }
    };
  }

  let open = true;
</script>

<div use:clickOutside={{ enabled: open, cb: () => open = false }}>
   <button>...</button>
   {#if open}
    <div>
      ...
    </div>
  {/if}
</div>

Key Press 🔗

Alpine:

<div @keydown.window.escape="open = false">
    ...
</div>

Svelte:

<script>
    function handleEscape({key}) {
        if (key === 'Escape') {
            open = false;
        }
    }
</script>

<svelte:window on:keyup={handleEscape} />

{#if open}
<div>...</div>
{/if}

This could also be done with a use: action similar to the Click Outside example.

Transitions 🔗

Alpine

<div
    x-transition:enter="transition ease-out duration-100"
    x-transition:enter-start="transform opacity-0 scale-95"
    x-transition:enter-end="transform opacity-100 scale-100"
    x-transition:leave="transition ease-in duration-75"
    x-transition:leave-start="transform opacity-100 scale-100"
    x-transition:leave-end="transform opacity-0 scale-95">
 ...
</div>

Svelte

<script>
  import { scale } from 'svelte/transition';
  import { cubicIn, cubicOut } from 'svelte/easing';
</script>

<div in:scale={{ duration: 100, start: 0.95, easing: cubicOut }}
     out:scale={{ duration: 75, start: 0.95, easing: cubicIn }}>
  Start is the scale value divided by 100.
</div>

Thanks for reading! If you have any questions or comments, please send me a note on Twitter. And if you enjoyed this, I also have a newsletter where I write about tech thoughts, interesting things I've read, and project updates each Thursday.

You can check out a recent issue, or enter your email below to subscribe.