ArkarDev

Directives in Svelte

Jul 21, 2023Arkar Kaung Myat
Svelte

This article is all about using directives in Svelte. We'll go over how they work and how you can use them in your projects.

In my last article, I write an introduction to Svelte, covering its reactivity and basic features. In this article, I will dive into directives in Svelte.

# Directives in Svelte

In Svelte, directives are special attributes that change how a DOM element works. Directives start with a : character. For example, on:click directive is used to handle click events on an element. What I love about Svelte is how it helps you make your app interactive without needing complex event listeners and state management.

# The on directive

Let’s take a look at the on directive and how we can listen and handle DOM elements with it. Let's say we want to bind the handleClick method to our button element. We can do this by adding the on:click directive to the button element and passing in the handleClick method as the JavaScript expression. For example:

<script>
  function handleClick() {
    alert('Click has been handled');
  }
</script>

<button on:click="{handleClick}">Click Me!</button>

This will bind the handleClick method to the click event of the button element so that when the button is clicked, the handleClick method will be called.

Handlers can also be declared inline, so instead of calling our handleClick function, we can directly call alert inline like this.

<script>
  function handleClick() {
    alert('Click has been handled');
  }
</script>

<button on:click="{() => alert('Click has been handled')}">Click Me!</button>

There are also modifiers in directives which are special postfixes that can be added to directives in Svelte. They are used to change the behavior of a directive.

on:eventname|modifiers={handler}

For example, let's say we want to prevent the default behavior of forms reloading when they submit. We can use preventDefault inside a modifier of the submit directive.

<script>
  function handleSubmit() {
    // Handle form submission
  }
</script>

<form on:submit|preventDefault="{handleSubmit}">
  <!-- Form fields go here -->
  <button type="submit">Submit</button>
</form>

Modifiers can add extra functionality to directives. Let’s take a look at the self modifier for example.

The self modifier is used to specify that an event listener should only be triggered if the event occurred on the element itself, not on any of its children. In the code example below, we have a green div element with a nested red div. When the green div is clicked, the handleClick method is called and an alert is shown.

However, when the red div is clicked, the handleClick method is not called. This is because the self modifier was used so the event listener only listens to events on the element itself, and not on its children.

<script>
  function handleClick(string) {
    alert(string);
  }
</script>
 
<button on:click="{() => {alert('Click has been handled')}}">Click Me!</button>
<div
  on:click|self="{() => {alert('Our green div was clicked!')}}"
  class="greenDiv"
>
  You can click the green.
  <div class="redDiv">You can't click the red</div>
</div>
 
<style>
  .greenDiv {
    background: LawnGreen;
    padding: 20px;
  }
  .redDiv {
    background: OrangeRed;
    padding: 20px;
  }
</style>

Here are the available modifiers from Svelte docs .

The following modifiers are available:

  • preventDefault — calls event.preventDefault() before running the handler
  • stopPropagation — calls event.stopPropagation(), preventing the event from reaching the next element
  • stopImmediatePropagation - calls event.stopImmediatePropagation(), preventing other listeners of the same event from being fired.
  • passive — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
  • nonpassive — explicitly set passive: false
  • capture — fires the handler during the capture phase instead of the bubbling phase
  • once — remove the handler after the first time it runs
  • self — only trigger handler if event.target is the element itself
  • trusted — only trigger handler if event.isTrusted is true. I.e. if the event is triggered by a user action.

# The bind directive

The bind directive is used to create a two-way binding between a variable and an element. This means that when the variable changes, the element will update, and when the element changes, the variable will update. For example, let's say we have an input element and we want to bind its value to a variable called name. We can do this by adding the bind:value directive to the input element and setting its value to name. For example:

<script>
  let name = '';
</script>

<input bind:value="{name}" />

This will create a two-way binding between the name variable and the value of the input element. When the user types in the input, the name variable will update, and when the name variable changes, the input's value will update.

The bind directive can also be used with other properties such as checked for checkboxes and class for classes. For example:

<script>
  let isChecked = false;
</script>

<input type="checkbox" bind:checked="{isChecked}" />

<div class="{isChecked ? 'checked' : ''}">Checkbox is {isChecked ? 'checked' : 'unchecked'}</div>

This will create a two-way binding between the isChecked variable and the checkbox's checked property. When the checkbox is checked or unchecked, the isChecked variable will update, and when the isChecked variable changes, the checkbox's checked property will update.

The bind directive is a powerful feature of Svelte that makes it easy to create interactive applications with minimal code.

Here are some examples of how you can use the bind method to manage form states more effectively.

// binding a select element
<select multiple bind:value={fillings}>
  <option value="Rice">Rice</option>
  <option value="Beans">Beans</option>
  <option value="Cheese">Cheese</option>
  <option value="Guac (extra)">Guac (extra)</option>
</select>

// binding a detail element
<details bind:open={isOpen}>
  <summary>Details</summary>
  <p>Something small enough to escape casual notice.</p>
</details>

You can check out more examples of binding elements here. Binding in Svelte .

# Style directives in Svelte

The style directive is a really helpful tool in Svelte. To use it, you add an attribute to an element in the format style:property={value}. The property part is the name of a CSS property and the value is what you want to set it to. Style directives are great because you can set individual properties without worrying about the whole CSS string.

<div
  style:position="absolute"
  style:top={position === 'absolute' ? '20px' : null}
  style:pointer-events={pointerEvents ? null : 'none'}
>
</div>

A common strategy for writing dynamic styles in Svelte is to apply the styles directly as an attribute. For example, you can make a box move around the screen with some events. Take a look at the example below.

<script>
		let mPosition = { x: 0, y: 0 };
		function handleMouseMove(e){
					mPosition.x = event.clientX / 10;
					mPosition.y = event.clientY / 10;
		}
	$:console.log(mPosition)
</script>

<div on:mousemove={handleMouseMove} class='container'>
	<div class="box" style="left: {mPosition.x}%; top: {mPosition.y}%" ></div>
</div>

<style>
	.container{
		width:100%;
		height:100vh;
	}
  .box {
    height: 80px;
    width: 80px;
    background: pink;
    position: absolute;
  }
</style>

In this example, we use the on:mousemove directive to call the handleMouseMove function whenever the mouse moves over the div element. Inside handleMouseMove, we update the x and y variables with the current mouse position. This works well but in Svelte we can use style directives to get the same behavior.

<script>
	// ...
</script>

<div on:mousemove={handleMouseEvent} class='container'>
	<div class="box" style:left="{mPosition.x}%" style:top="{mPosition.y}%"></div>
</div>

<style>
		// ...
</style>

You can use style directives with any CSS property, including CSS custom property definitions.

<div style:--bg="red" class="box"></div>
<div style:--bg="green" class="box"></div>
<div style:--bg="orange" class="box"></div>

<style>
	.box{
		width:100px;
		aspect-ratio:1/1;
		background:var(--bg);
	}
</style>

You can check out more about style directives here in the Official documentation .

# Component directives in Svelte

Svelte also has directives that apply to the whole component rather than a specific element within it. To declare these directives, use the export keyword in the component's script tag. For example, suppose we have a component called Button that we want a user to click. We can make a clickable directive for the component like this:

// Button.svelte
<script>
  export let handleClick;

  function handleClickWrapper(event) {
    event.preventDefault();
    handleClick(event);
  }
</script>

<button on:click="{handleClickWrapper}"><slot /></button>

We made a function called handleClickWrapper that stops the button from doing its usual thing when clicked, and instead runs the handleClick function that we made in the component's script tag. Then we added the clickable thing to the button element, which makes the button run the handleClickWrapper function when it's clicked.

Now we can use the Button in our app and give it a handleClick function as a prop.

// app.svelte
<script>
  import Button from './Button.svelte';

  function handleClick(event) {
    alert('Button clicked!');
  }
</script>

<Button handleClick="{handleClick}">Click Me!</Button>

This will create a button that displays "Click Me!" and calls the handleClick function when clicked.

Component-level directives are a powerful feature of Svelte that allows you to create reusable components with custom behavior.

Another example is that let’s say you are building a UI library with Svelte and you want to allow customizations. By using component-level directives, you can also pass styles as props to components for the purposes of theming, using CSS custom properties.

Svelte's implementation is essentially syntactic sugar for adding a wrapper element. For example,

// You can do this
<Slider bind:value min={0} --rail-color="black" --track-color="rgb(0, 0, 255)" />

// instead of this
<div style="display: contents; --rail-color: black; --track-color: rgb(0, 0, 255)">
  <Slider bind:value min={0} max={100} />
</div>

<!-- Slider.svelte -->

<style>
  .potato-slider-rail {
    background-color: var(--rail-color, var(--theme-color, 'purple'));
  }
</style>

Thank you for reading this article on directives in Svelte. I hope you found it informative and helpful. Make sure you read more about directives in the official docs .