How to make a modal component in Svelte and Tailwind CSS.

How to make a modal component in Svelte and Tailwind CSS.

How to make a modal component in Svelte and Tailwind CSS.

What you'll learn from this tutorial:

  • An idea of how to create reusable components in Svelte.
  • An understanding of how to pass events from child to parent components, and passing props down to child components.
  • An idea of using slots in components.

What you'll need:

In this tutorial, we'll use TailwindCSS and the starter project from SvelteKit, I'm also going to assume you have this set up for this tutorial.

Let's start! 🎉

<div class="modal z-50 fixed w-full h-full top-0 left-0 flex items-center justify-center p-8 lg:p-0">
  <div class="modal-overlay fixed w-full h-full bg-gray-900 opacity-50"></div>
  <div class="bg-white w-full lg:h-max lg:w-1/2  mx-auto rounded-lg shadow-xl z-50 overflow-y-auto">
    <div class="head bg-gray-100 py-5 px-8 text-2xl font-extrabold">
    </div>
    <div class="content p-8">
    </div>
  </div>
</div>

In the above code snippet is our markup for our modal, this is just regular HTML with TailwindCSS classes. As you'll know from using modals, modals need a few different pieces of functionality. Firstly we need to be able to close and open the modal, so we'll work on that next by adding an open prop that can be passed into the component.

<script>
  export let open = false
</script>

We'll need to use this prop to conditionally show our markup, so all we need to do is wrap an if statement around our HTML.

{#if open}
  <div class="modal z-50 fixed w-full h-full top-0 left-0 flex items-center justify-center p-8 lg:p-0">
    <div class="modal-overlay fixed w-full h-full bg-gray-900 opacity-50"></div>
    <div class="bg-white w-full lg:h-max lg:w-1/2  mx-auto rounded-lg shadow-xl z-50 overflow-y-auto">
      <div class="head bg-gray-100 py-5 px-8 text-2xl font-extrabold">
      </div>
      <div class="content p-8">
      </div>
    </div>
  </div>
{/if}

Our markup will now be rendered by Svelte if open is set to true. However, what if we want to now close our modal? We'll need a way telling our parent component that the modal close button has been clicked. In the svelte API, we can use the createEventDispatcher to dispatch an event to our parent component, just like I show below.

<script>
  import { createEventDispatcher } from 'svelte'

  export let open = false

  const dispatch = createEventDispatcher()
</script>
{#if open}
  <div class="modal z-50 fixed w-full h-full top-0 left-0 flex items-center justify-center p-8 lg:p-0">
    <div class="modal-overlay fixed w-full h-full bg-gray-900 opacity-50"></div>
    <div class="bg-white w-full lg:h-max lg:w-1/2  mx-auto rounded-lg shadow-xl z-50 overflow-y-auto">
      <div class="head bg-gray-100 py-5 px-8 text-2xl font-extrabold">
        <button class="p-2 bg-gray-200 hover:bg-gray-300 rounded-full ml-4" on:click={() => dispatch('close')}>
          <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
        </button>
      </div>
      <div class="content p-8">
      </div>
    </div>
  </div>
{/if}

Now in our markup, we can see a button with an on:click event which dispatches a close event using our event dispatcher. Our parent component can now listen for this, and supply a callback function to set its internal modal open state to false. There are a bunch more options we could include into this modal, but to keep it simple, let's add a title prop and few slots which can be used as placeholders for the actual contents of the modal. Let's firstly add our slots and title props.

  <div class="modal z-50 fixed w-full h-full top-0 left-0 flex items-center justify-center p-8 lg:p-0">
    <div class="modal-overlay fixed w-full h-full bg-gray-900 opacity-50"></div>
    <div class="bg-white w-full lg:h-max lg:w-1/2  mx-auto rounded-lg shadow-xl z-50 overflow-y-auto">
      <div class="flex justify-between items-center head bg-gray-100 py-5 px-8 text-2xl font-extrabold">
        {title}
        <button class="p-2 bg-gray-200 hover:bg-gray-300 rounded-full ml-4" on:click={() => dispatch('close')}>
          <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
        </button>
      </div>
      <div class="content p-8">
        <slot name="body" />
      </div>
    </div>
  </div>
<script>
  import { createEventDispatcher } from 'svelte'

  export let open = false
  export let title = ''

  const dispatch = createEventDispatcher()
</script>

As you can see above I've added a slot with the name of 'body'. This is where we can place our actual modal content when using this component. Svelte allow us to have as many slots in your components as long as they're named. You can even provide fallback content into these slots to cover the case of content not being supplied into the component when it's being used.

So now we can open and close the modal, pass in a title to the modal top bar, and we have a slot setup for our modal content. Now let me show you how you'd use this component within your codebase.

<button on:click={() => handleToggleModal()}>Open modal</button>

<Modal
  title="Edit your details"
  open={showModal}
  on:close={() => handleToggleModal()}
>
  <svelte:fragment slot="body">
    This is content inside my modal! 👋
  </svelte:fragment>
</Modal>
<script>
  import Modal from '../components/Modal.svelte'

  let showModal = false

  const handleToggleModal = () => {
    showModal = !showModal
  }
</script>

And... there you go! You've just made a modal component that you can re-use across your code-base.