DEV Community

Cover image for Creating Beautiful User Interfaces With Material Design for Bootstrap 4 & 5 (MDB)
Boone Cabal
Boone Cabal

Posted on • Edited on

Creating Beautiful User Interfaces With Material Design for Bootstrap 4 & 5 (MDB)

Combining multiple front-end technologies can be challenging due to limited documentation. We’ll solve such a problem by creating a small web application that combines Svelte–a small but powerful front-end technology akin to React and Vue–with the elegant Material Design for Bootstrap 4 & 5 (MDB) framework. It will feature a navigation bar with an embedded dropdown menu that facilitates switching between three different content panes, each populated with stylish MDB components, resulting in an aesthetically appealing and responsive User Interface (UI).

Prerequisites

This article is for the intermediate-to-advanced level web developer who’s conversant in CSS and Bootstrap 5. If you’re not well-versed in either, don’t worry, as this article provides links to useful documentation.

We're going to build our Svelte application using the Svelte REPL sandbox (or just REPL) at svelte.dev. I recommend checking out all the great documentation at svelte.dev, like its Examples section showcasing Svelte's many features, as well as the cool interactive tutorial at learn.svelte.dev.

Here are resources to consider looking at:

Step One – Creating a Svelte/MDB Boilerplate in REPL

When you navigate to the Svelte REPL sandbox and click the REPL link, it takes you to a Hello World application with the following code:

(In App.svelte)

<script>

  let name = 'world';

</script>

<h1>Hello {name}!</h1>

Enter fullscreen mode Exit fullscreen mode

Let's link Bootstrap 5, FontAwesome, a Google font, and MDB stylesheets using a special <svelte:head> element and the Popper and MDB JavaScripts. Replace this markup with the following:

<script>

  // Component initialization code goes here.

</script>

<svelte:head>

  <!-- Bootstrap CSS -->
  <link
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
    rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
    crossorigin="anonymous"
    />

  <!-- Font Awesome -->
  <link
    href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
    rel="stylesheet"
    />

  <!-- Google Fonts -->
  <link
    href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
    rel="stylesheet"
    />

  <!-- MDB -->
  <link
    href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/7.3.2/mdb.min.css"
    rel="stylesheet"
    />

</svelte:head>

<body>

  <header class="mb-5">

    <!-- navbar -->

  </header>
  <!-- header -->

  <!-- main -->
  <main class="container py-3">

    <!-- pane content -->

  </main>
  <!-- main -->

  <!--  Bootstrap Bundle with Popper -->
  <script
    src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
    crossorigin="anonymous"></script>

  <!-- MDB -->
  <script
    type="text/javascript"
    src="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/7.3.2/mdb.umd.min.js"
    ></script>

</body>

<style>

  /* Scripts */

</style>

Enter fullscreen mode Exit fullscreen mode

When you paste code blocks from this article into REPL, it won’t be formatted with the proper whitespace, so you might want to fold the pasted code block, select it, and then tab it in. I hope they consider adding a “format HTML” button.

It’s important to understand that the above-linked files might be obsolete when you read this. I recommend replacing the above <link> and <script> elements with the markup from the MDB installation page. It provides detailed instructions about how to do this.

It’s wise to save this REPL with a meaningful name like Svelte Boilerplate, fork it, and then rename the forked copy to the name of this project. This is akin to creating a commit.

Here is the REPL for this step.

Step Two – Adding Navigation Bar and Cards

Now we'll flesh out our application by adding an MDB navbar, and an accordion as our content for pane 1–the default pane. We'll start with the navigation bar (navbar).

MDB Navigation Bar

Our navbar will have a left-aligned brand logo and a right-aligned dropdown menu. Clicking a menu item switches between panes, and the active pane will be displayed in the dropdown's display text.

We'll build our navbar over several steps, starting with a static navbar containing dummy menu items. Let's start by replacing our <!-- navbar --> block with the following code:

<!-- navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-body-tertiary">

  <div class="container-fluid">

    <a class="navbar-brand" href="navbar_Click">Branding</a>

    <!-- Dropdown Menu -->
    <ul class="navbar-nav">

      <div class="nav-item dropdown pe-3">

      <a
        data-mdb-dropdown-init
        class="nav-link dropdown-toggle"
        id="navbarDropdownMenuLink"
        role="button"
        aria-expanded="false"
        >Selected Pane</a>
      <!-- nav-link (dropdown button) -->

      <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">

        <li class="dropdown-item">
          <button
            type="button"
            class="btn btn-link"
            data-mdb-ripple-init
            value="Pane 1"
            >Pane 1</button>
        </li>
        <!-- dropdown-item -->

        <li class="dropdown-item">
          <button
            type="button"
            class="btn btn-link"
            data-mdb-ripple-init
            value="Pane 2"
            >Pane 2</button>
        </li>
        <!-- dropdown-item -->

        <li class="dropdown-item">
          <button
            type="button"
            class="btn btn-link"
            data-mdb-ripple-init
            value="Pane 3"
            >Pane 3</button>
        </li>
        <!-- dropdown-item -->

      </ul>
      <!-- dropdown-menu -->

    </ul>
    <!-- Dropdown Menu -->

  </div>
  <!-- container-fluid -->

</nav>
<!-- navbar -->

Enter fullscreen mode Exit fullscreen mode

MDB Accordion

Let’s add an MDB accordion as filler content to pane 1. Replace the <!-- pane content --> block with the following code, copied from the MDB accordion documentation.

<!-- pane 1 -->
<div class="accordion" id="accordionExampleY">

  <div class="accordion-item">

    <h2 class="accordion-header" id="headingOneY">
      <button data-mdb-collapse-init class="accordion-button" type="button" data-mdb-toggle="collapse"
        data-mdb-target="#collapseOneY" aria-expanded="true" aria-controls="collapseOneY">
      <i class="fas fa-question-circle fa-sm me-2 opacity-70"></i>Accordion Item #1
      </button>
    </h2>
    <!-- accordion-header -->

    <div id="collapseOneY" class="accordion-collapse collapse show" aria-labelledby="headingOneY"
      data-mdb-parent="#accordionExampleY">
      <div class="accordion-body">
        <strong>This is the first item's accordion body.</strong> It is hidden by
        default, until the collapse plugin adds the appropriate classes we use to
        style each element. These classes control the overall appearance and
        the visibility via CSS transitions. You can modify any of this with
        custom CSS or overriding our default variables. It's also worth noting that
        nearly any HTML can go within the <code>.accordion-body</code>, though the
        transition does limit overflow.
      </div>
    </div>
    <!-- accordion-collapse -->

  </div>
  <!-- accordion-item -->

  <div class="accordion-item">

    <h2 class="accordion-header" id="headingTwoY">
      <button data-mdb-collapse-init class="accordion-button collapsed" type="button" data-mdb-toggle="collapse"
        data-mdb-target="#collapseTwoY" aria-expanded="false" aria-controls="collapseTwoY">
      <i class="fas fa-question-circle fa-sm me-2 opacity-70"></i>Accordion Item #2
      </button>
    </h2>
    <!-- accordion-header -->

    <div id="collapseTwoY" class="accordion-collapse collapse" aria-labelledby="headingTwoY"
      data-mdb-parent="#accordionExampleY">
      <div class="accordion-body">
        <strong>This is the second item's accordion body.</strong> It is hidden by
        default, until the collapse plugin adds the appropriate classes we use to
        style each element. These classes control the overall appearance and
        the visibility via CSS transitions. You can modify any of this with
        custom CSS or overriding our default variables. It's also worth noting that
        nearly any HTML can go within the <code>.accordion-body</code>, though the
        transition does limit overflow.
      </div>
    </div>
    <!-- accordion-collapse -->

  </div>
  <!-- accordion-item -->

  <div class="accordion-item">

    <h2 class="accordion-header" id="headingThreeY">
      <button data-mdb-collapse-init class="accordion-button collapsed" type="button" data-mdb-toggle="collapse"
        data-mdb-target="#collapseThreeY" aria-expanded="false" aria-controls="collapseThreeY">
      <i class="fas fa-question-circle fa-sm me-2 opacity-70"></i>Accordion Item #3
      </button>
    </h2>
    <!-- accordion-heading -->

    <div id="collapseThreeY" class="accordion-collapse collapse" aria-labelledby="headingThreeY"
      data-mdb-parent="#accordionExampleY">
      <div class="accordion-body">
        <strong>This is the third item's accordion body.</strong> It is hidden by
        default, until the collapse plugin adds the appropriate classes we use to
        style each element. These classes control the overall appearance and
        the visibility via CSS transitions. You can modify any of this with
        custom CSS or overriding our default variables. It's also worth noting that
        nearly any HTML can go within the <code>.accordion-body</code>, though the
        transition does limit overflow.
      </div>
    </div>
    <!-- accordion-collapse -->

  </div>
  <!-- accordion-item -->

</div>
<!-- accordion -->
<!-- pane 1 -->

Enter fullscreen mode Exit fullscreen mode

Here is the REPL for this step.

Step Three – Populating Navbar Dropdown Menu

Now, we'll use some Svelte template code to dynamically populate the navbar’s dropdown menu and synchronize its display text with the name of the active pane.

First, let's create a ButtonTypes enumerated type, which represents the currently active pane, and a mode variable of type ButtonTypes. Replace your <script> block with the following code:

<script>
  const ButtonTypes = {
    PANEL_1: 'Panel 1',
    PANEL_2: 'Panel 2',
    PANEL_3: 'Panel 3'
  };
  let mode = ButtonTypes.PANEL_1;
</script>

Enter fullscreen mode Exit fullscreen mode

I use the term “makeshift” because a true enumerated type is only available in TypeScript. Unfortunately, svelte.dev doesn't support TypeScript as of this writing.

Next, let’s dynamically create our dropdown menu items and set the menu's display text to the name of the active pane. Replace the <!-- Dropdown Menu --> comment block with the following code:

<!-- Dropdown Menu -->
<ul class="navbar-nav">

  <div class="nav-item dropdown pe-3">

    <a
      data-mdb-dropdown-init
      class="nav-link dropdown-toggle"
      id="navbarDropdownMenuLink"
      role="button"
      aria-expanded="false"
      >{mode}</a>

    <!-- nav-link (dropdown button) -->
    <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">

      {#each Object.keys(ButtonTypes) as buttonTypeKey}

        <li class="dropdown-item">

          <button
            type="button" 
            class="btn btn-link" 
            data-mdb-ripple-init
            value={ButtonTypes[buttonTypeKey]}
          >{ButtonTypes[buttonTypeKey]}</button>

        </li>
        <!-- dropdown-item -->

      {/each}

    </ul>
    <!-- dropdown-menu -->

  </div>

</ul>
<!-- Dropdown Menu -->

Enter fullscreen mode Exit fullscreen mode

Because ButtonTypes is an object and not a true enumerated type, we iterate over its properties, assigning each menu item's display text.

Here is the REPL for this step.

Step Four – Switching Between Panes

Now we're going to augment our dropdown menu with functionality to switch between panes. For now we’ll create filler content for panes 2 and 3 with <h2> elements. (We already have an accordion in pane 1.) Additionally, we’ll add an if statement to our <main> element that uses our mode enum to decide which pane to display.

First, let’s create a click event-handler function named navbar_Click and attach it to each menu item. navbar_Click changes the active pane by assigning a new value to mode. Replace your <script> element with the following code:

<script>
  const ButtonTypes = {
    PANEL_1: 'Panel 1',
    PANEL_2: 'Panel 2',
    PANEL_3: 'Panel 3'
  };
  let mode = ButtonTypes.PANEL_1;

  const navbar_Click = function(e) {
    console.log(`e.target.value = ${e.target.value}`);
    mode = e.target.value;
  }
</script>

Enter fullscreen mode Exit fullscreen mode

Next, let’s attach navbar_Click to each dropdown menu item <button> as it is created. Replace your <!-- Dropdown Menu --> block with the following code.

<!-- Dropdown Menu -->
<ul class="navbar-nav">

  <div class="nav-item dropdown pe-3">

    <a
      data-mdb-dropdown-init
      class="nav-link dropdown-toggle"
      id="navbarDropdownMenuLink"
      role="button"
      aria-expanded="false"
      >{mode}</a>
    <!-- nav-link (dropdown button) -->

    <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">

      {#each Object.keys(ButtonTypes) as buttonTypeKey}

      <li class="dropdown-item">

        <button 
          on:click={navbar_Click}
          type="button" 
          class="btn btn-link" 
          data-mdb-ripple-init
          value={ButtonTypes[buttonTypeKey]}
        >{ButtonTypes[buttonTypeKey]}</button>

      </li>
      <!-- dropdown-item -->

      {/each}

    </ul>
    <!-- dropdown-menu -->

  </div>
  <!-- nav-item -->

</ul>
<!-- Dropdown Menu -->

Enter fullscreen mode Exit fullscreen mode

Finally, let’s add an if statement to <main> that uses mode to determine which pane to display. Update your <main> element with the following if statement:

<!-- main -->
<main class="container py-3">

  {#if mode === ButtonTypes.PANEL_1}

    <!-- pane 1 -->

    <!-- accordion ... -->

    <!-- pane 1 -->

  {:else if mode === ButtonTypes.PANEL_2}

    <!-- pane 2 -->
    <h2>Panel 2</h2>
    <!-- pane 2 -->

  {:else if mode === ButtonTypes.PANEL_3}

    <!-- pane 3 -->
    <h2>Panel 3</h2>
    <!-- pane 3 -->

  {/if} 

</main>
<!-- main -->

Enter fullscreen mode Exit fullscreen mode

You’ll want to replace the above <!-- accordion ... --> block with either the accordion markup you used earlier in this article, or, preferably, that found in the MDB accordion documentation.

Test the UI and make sure the dropdown menu properly switches between panes.

Here is the REPL for this step.

Step Five – Disabling the Active Panel’s Menu Item

Sometimes it’s useful to disable or hide elements in a UI to prevent invalid actions or give the user hints about how the user interface works. We’ll disable the dropdown menu item representing the active pane, thus helping the user understand which pane is active.

First, let’s add a disableMenuItem boolean variable to our <script>. Replace your <script> with the following code:

<script>
  const ButtonTypes = {
    PANEL_1: 'Panel 1',
    PANEL_2: 'Panel 2',
    PANEL_3: 'Panel 3'
  };
  let mode = ButtonTypes.PANEL_1;

  let disableMenuItem = false;

  const navbar_Click = function(e) {
    console.log(`e.target.value = ${e.target.value}`);
    mode = e.target.value;
  }
</script>

Enter fullscreen mode Exit fullscreen mode

Next, let's add a disabled attribute to the <button> menu item, assigning it to disableMenuItem. Replace the <!-- Dropdown Menu --> block with the following code:

<!-- Dropdown Menu -->
<ul class="navbar-nav">

  <div class="nav-item dropdown pe-3">

    <a
      data-mdb-dropdown-init
      class="nav-link dropdown-toggle"
      id="navbarDropdownMenuLink"
      role="button"
      aria-expanded="false"
      >{mode}</a>
    <!-- nav-link (dropdown button) -->

    <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">

      {#each Object.keys(ButtonTypes) as buttonTypeKey}

      <li class="dropdown-item">

        <button 
          on:click={navbar_Click}
          disabled={disableMenuItem = ButtonTypes[buttonTypeKey] === mode}
          type="button" 
          class="btn btn-link" 
          data-mdb-ripple-init
          value={ButtonTypes[buttonTypeKey]}
          >{ButtonTypes[buttonTypeKey]}</button>

      </li>
      <!-- dropdown-item -->

      {/each}

    </ul>
    <!-- dropdown-menu -->

  </div>
  <!-- nav-item dropdown -->

</ul>
<!-- Dropdown Menu -->

Enter fullscreen mode Exit fullscreen mode

Here is the REPL for this step.

Step Six – Fleshing Out Panels With MDB Components

Now we'll flesh out our UI by adding MDB components, taken directly from the MDB website documentation–to panes 2 and 3. Let's add a card gallery to pane 2 and feature badges to pane 3. (We’ve already added an MDB accordion to pane 1, so we only need content for panes 2 and 3.)

Panel 2 – MDB Cards

Replace your <!-- pane 2 --> block with the following card gallery markup.

I’ve added whitespace and comments to the following code.

<!-- pane 2 -->
<div class="row row-cols-1 row-cols-md-3 g-4">

  <div class="col">

    <div class="card">

      <img 
        src="https://mdbcdn.b-cdn.net/img/new/standard/city/041.webp"
        class="card-img-top" 
        alt="Hollywood Sign on The Hill"/>

      <div class="card-body">

        <h5 class="card-title">Card title</h5>

        <p class="card-text">
          This is a longer card with supporting text below as a natural lead-in to
          additional content. This content is a little bit longer.
        </p>

      </div>
      <!-- card-body -->

    </div>
    <!-- card -->

  </div>
  <!-- col -->

  <div class="col">

    <div class="card">

      <img 
        src="https://mdbcdn.b-cdn.net/img/new/standard/city/042.webp" 
        class="card-img-top" 
        alt="Palm Springs Road"/>

      <div class="card-body">

        <h5 class="card-title">Card title</h5>

        <p class="card-text">
          This is a longer card with supporting text below as a natural lead-in to
          additional content. This content is a little bit longer.
        </p>

      </div>
      <!-- card-body -->

    </div>
    <!-- card -->

  </div>
  <!-- col -->

<div class="col">

    <div class="card">

      <img 
        src="https://mdbcdn.b-cdn.net/img/new/standard/city/043.webp" 
        class="card-img-top" 
        alt="Los Angeles Skyscrapers"/>

      <div class="card-body">

        <h5 class="card-title">Card title</h5>

        <p class="card-text">This is a longer card with supporting text below as a natural lead-in to additional content.</p>

      </div>
      <!-- card-body -->

    </div>
    <!-- card -->

  </div>
  <!-- col -->

  <div class="col">

    <div class="card">

      <img 
        src="https://mdbcdn.b-cdn.net/img/new/standard/city/044.webp" 
        class="card-img-top" 
        alt="Skyscrapers"/>

      <div class="card-body">

        <h5 class="card-title">Card title</h5>

        <p class="card-text">
          This is a longer card with supporting text below as a natural lead-in to
          additional content. This content is a little bit longer.
        </p>

      </div>

    </div>
    <!-- card -->

  </div>
  <!-- col -->

</div>
<!-- row -->
<!-- pane 2 -->

Enter fullscreen mode Exit fullscreen mode

Panel 3 – MDB Feature Badges

Replace your <!-- pane 3 --> block with the following feature badges markup:

<!-- pane 3 -->
<section class="mb-5">

  <div class="row gx-lg-5">

    <div class="col-lg-6 mb-5">
      <div class="d-flex align-items-start">
        <div class="flex-shrink-0">
          <div class="p-3 badge-primary rounded-4">
            <i class="fas fa-cloud-upload-alt fa-lg text-primary fa-fw"></i>
          </div>
        </div>
        <div class="flex-grow-1 ms-4">
          <p class="fw-bold mb-1">Tutorials</p>
          <p class="text-muted mb-1">
            Dozens of free tutorials to help you discover the full potential of MDB.
          </p>
          <small><a href="google.com">Learn more</a></small>
        </div>
      </div>

    </div>
    <!-- col -->

    <div class="col-lg-6 mb-5">
      <div class="d-flex align-items-start">
        <div class="flex-shrink-0">
          <div class="p-3 badge-primary rounded-4">
            <i class="fas fa-database fa-lg text-primary fa-fw"></i>
          </div>
        </div>
        <div class="flex-grow-1 ms-4">
          <p class="fw-bold mb-1">Integrations</p>
          <p class="text-muted mb-1">
            MDB is integrated with all major technologies and tools.
          </p>
          <small><a href="google.com">Learn more</a></small>
        </div>
      </div>
    </div>
    <!-- col -->

    <div class="col-lg-6 mb-5">
      <div class="d-flex align-items-start">
        <div class="flex-shrink-0">
          <div class="p-3 badge-primary rounded-4">
            <i class="fas fa-stream fa-lg text-primary fa-fw"></i>
          </div>
        </div>
        <div class="flex-grow-1 ms-4">
          <p class="fw-bold mb-1">Backend friendly</p>
          <p class="text-muted mb-1">
            MDB is designed to be seamlessly integrated and used with the backend.
          </p>
          <small><a href="google.com">Learn more</a></small>
        </div>
      </div>
    </div>
    <!-- col -->

    <div class="col-lg-6 mb-5">
      <div class="d-flex align-items-start">
        <div class="flex-shrink-0">
          <div class="p-3 badge-primary rounded-4">
            <i class="fas fa-copy fa-lg text-primary fa-fw"></i>
          </div>
        </div>
        <div class="flex-grow-1 ms-4">
          <p class="fw-bold mb-1">Support and community</p>
          <p class="text-muted mb-1">
            The MDB team and our global community are there to support you.
          </p>
          <small><a href="google.com">Learn more</a></small>
        </div>
      </div>
    </div>
    <!-- col -->

  </div>
  <!-- row-->

</section>
<!-- pane 3 -->

Enter fullscreen mode Exit fullscreen mode

Another great benefit of MDB is the relative ease with which you can create responsive web pages. Try horizontally resizing the sandbox Result window and notice how the components resize and reposition as breakpoints are crossed.

Here is the REPL for this step.

Cleaning Up Our Code

Now that we’ve finished creating a winsome UI, we’ll clean up and refactor our code. First, we’ll divide our code into multiple files by using <svelte:component>.

Step One – Using svelte:component

Svelte offers a very useful <svelte:component> element that lets us pass a component to display using the this directive.

Check out this great example of using svelte:component and Working with DOM nodes using the bind:this={dom_node} directive for more about <svelte:component> and the this directive, respectively.

First, let’s create three new components representing each of our panes, Move your <!-- pane 1 --> code block into a new Panel1.svelte, <!-- pane 2 --> into Panel2.svelte, and <!-- pane 3 --> into Panel3.svelte.

Next, let’s import the new panel components into App.svelte. Replace your <script> block with the following code:

(In App.svelte)

<script>
  import Panel1 from './Panel1.svelte';
  import Panel2 from './Panel2.svelte';
  import Panel3 from './Panel3.svelte';

  const ButtonTypes = {
    PANEL_1: 'Panel 1',
    PANEL_2: 'Panel 2',
    PANEL_3: 'Panel 3'
  };
  let mode = ButtonTypes.PANEL_1;

  const navbar_Click = function(e) {
    console.log(`e.target.value = ${e.target.value}`);
    mode = e.target.value;
  }
</script>

Enter fullscreen mode Exit fullscreen mode

Finally, let’s update App.svelte to use <svelte:component> elements to display our new pane components. Replace your <main> with the following code:

(In App.svelte)

<!-- main -->
<main class="container py-3">

  {#if mode === ButtonTypes.PANEL_1}

  <!-- pane 1 -->
  <svelte:component this={Panel1} />
  <!-- pane 1 -->

  {:else if mode === ButtonTypes.PANEL_2}

  <!-- pane 2 -->
  <svelte:component this={Panel2} />
  <!-- pane 2 -->

  {:else if mode === ButtonTypes.PANEL_3}
  <!-- pane 3 -->
  <svelte:component this={Panel3} />
  <!-- pane 3 -->

  {/if}

</main>
<!-- main -->

Enter fullscreen mode Exit fullscreen mode

Here is the REPL for this step.

Step Two – Creating Store For mode Variable

Sometimes it’s more convenient to use some application-wide state (a global) instead of propagating it from a parent component to a child. Stores are the Svelte equivalent of readable and writable global variables. To use a store, simply import writable from svelte/store, and then create a store variable using writable. Furthermore, you must prefix a store variable with $ when using it.

This is all you need to know about stores for this article. The MDB article Working with Svelte stores provides a thorough explanation of stores.

We need to move some code around. First, let’s create a stores.js file that contains ButtonTypes, imports writable, and redefines mode as a writable store. Create stores.js and add the following code:

(In stores.js)

import { writable } from 'svelte/store';

export let ButtonTypes = {
  PANEL_1: 'Panel 1',
  PANEL_2: 'Panel 2', 
  PANEL_3: 'Panel 3' 
};

export const mode = writable(ButtonTypes.PANEL_1);

Enter fullscreen mode Exit fullscreen mode

Next, let’s update our App.svelte by importing mode and ButtonTypes from stores.js.

(In App.svelte)

<script>
  import { mode, ButtonTypes } from './stores.js'
  import Panel1 from './Panel1.svelte';
  import Panel2 from './Panel2.svelte';
  import Panel3 from './Panel3.svelte';

  let disableMenuItem = false; 

  const navbar_Click = function(e) { 
    console.log(`e.target.value = ${e.target.value}`);
    mode = e.target.value;
  }
</script>

Enter fullscreen mode Exit fullscreen mode

This will result in a syntax error. To fix it, type CTRL+f, and then search-and-replace all instances of mode with $mode. However, you do not want to apply this change to the mode in the import statement.

Here is the REPL for this step.

Step Three – Creating a Layout Component

Now we'll learn how to create generic templates using the <slot> element.

An Example of Using <slot>

Unlike template processors like Python Flask, Svelte doesn't use template inheritance; instead, it uses composition. Therefore, we don’t inherit from a parent template; instead, any component needing some shared layout will contain a reference to a layout component.

We'll digress and walk through an example using <slot>. First, let's create a BaseLayout component with some shared structure. It will use <slot> (or <slot />) elements as placeholders wherein a Page component will inject code.

(In BaseLayout.svelte)

<script>
  export title;
</script>

<div class="layout">

  <header>
    <h1>{title}</h1>
  </header>

  <main>
    <slot />
  </main>

  <footer>
    <!-- footer content ... -->
  </footer>

</div>

Enter fullscreen mode Exit fullscreen mode

This BaseLayout component defines a header, content area, and footer, and an exported title property.

Now let’s create a Page component that contains BaseLayout component instance and injects code into it.

(In Page.svelte)

<script>
  import BaseLayout from './BaseLayout.svelte';
  let title = "Home Page";
</script>

<BaseLayout {title}>
  <p>This is the home page content.</p>
</BaseLayout>

Enter fullscreen mode Exit fullscreen mode

Even the most casual observer will notice how we bind Page's title variable to Layout's title property using the {title} directive, illustrating passing state from a parent (Page) component to a child (BaseLayout).

You can find a thorough explanation of passing state between parent and child components in the article Sharing State Between Components.

Using <slot> in Our Application

Let's use <slot> in our application. First, create a new Layout.svelte file and move all the code from App.svelte into it. Replace the <!-- main --> block with the following code.

(In Layout.svelte)

<!-- main -->
<main class="container py-3">

  <slot />

</main>
<!-- main -->

Enter fullscreen mode Exit fullscreen mode

Next, replace the <script> element with the following code:

(In Layout.svelte)

<script>
  import { mode, ButtonTypes } from './stores.js';

  // Variables 
  let disableMenuItem = false;

  // DOM event-handlers
  const navbar_Click = function(e) {
    console.log(`e.target.value = ${e.target.value}`); 
    $mode = e.target.value;  
  }
</script>

Enter fullscreen mode Exit fullscreen mode

Great! You refactored a base layout into its own component.

Finally, let’s update App.svelte. Import Layout, and use it as an element to inject an if statement into its inner HTML. Replace all your App.svelte code with the following code:

(In App.svelte)

<script>
  import { mode, ButtonTypes } from './stores.js';
  import Panel1 from './Panel1.svelte';
  import Panel2 from './Panel2.svelte';
  import Panel3 from './Panel3.svelte';
  import Layout from './Layout.svelte';

</script>

<Layout>

  {#if $mode === ButtonTypes.PANEL_1}

    <!-- pane 1 -->
    <svelte:component this={Panel1} />
    <!-- pane 1 -->


  {:else if $mode === ButtonTypes.PANEL_2}

    <!-- pane 2 -->
    <svelte:component this={Panel2} />
    <!-- pane 2 -->


  {:else if $mode === ButtonTypes.PANEL_3}

    <!-- pane 3 -->
    <svelte:component this={Panel3} />
    <!-- pane 3 -->

  {/if}

</Layout>

Enter fullscreen mode Exit fullscreen mode

Check out slot fallbacks and named slots for great examples of using <slot>.

Here is the REPL for this step.

Nested components let you compose more complex components, making it easier to understand and maintain your codebase.

Conclusion

We covered a lot in this article. Now you have the tools to create dainty, pleasurable UIs with MDB and compose nested components. Have fun using Svelte, friend. Now leave, please.

Top comments (0)