Tutorial: Tags in Svelte
Tags: tutorial, svelte, javascript
Published: 2023-07-26 16:14:23

---
title: "Tutorial: Tags in Svelte"
published: true
tags:
- tutorial
- svelte
- javascript
---

![Tags input](https://barim.us/img/svelte-tagsinput.gif)

I want to code a simple tags input component in Svelte like the animation above.

- An input text where I can type words.
- As soon as I type a comma or Enter, the input turns into a "tag".
- The tag appears next to the input text with a small cross to delete it.

I can retrieve the list of tags in an easy-to-use data structure: for example, an array of string.

I'll use the [Svelte Repl](https://svelte.dev/repl) for this tutorial.

## Exploring possibilities

In Svelte, `on:keydown` triggers a function on every keypress. So I create a small piece of code to highlight this behavior:

```javascript
<script>
function pressed(ev){
    console.info(ev.key);
};
</script>
<input on:keydown={pressed}/>
```

`keydown` call `pressed` function passing an `event` object as parameter. I'm only interested in the `key` information inside this object as it contains the key pressed by the user.

Each press on the keyboard, with the cursor in the text field, will cause a display in the console.

So, I can monitor the input waiting for a comma by doing a test on `key`:

```javascript
<script>
function pressed(ev){
    if(ev.key === ',') {
        console.info("VIRGULE!!!")
    }
};
</script>
<input on:keydown={pressed}/>
```

We've got something, now let's use that.

## Into the heart of the matter

When you press comma, that's when you have a tag to add: so I take the content of the input field and I add it to my list.

To see what I'm doing, I add a line with '{tags}' which will display the list of saved tags:

```javascript
<script>
let tags = [];                      // save tags here
let value = "";                     // input value
function pressed(ev){
    if(ev.key === ',') {            // comma ?
        tags = [...tags, value];    // add to the list
        value = "";                 // and clean input
    }
};
</script>
{tags}
<input on:keydown={pressed} bind:value/>
```

Small problem: the comma remains in the field despite the cleaning.

The `keydown` intercepts the event before the key is taken into account by the input field. So our processing is fired before the comma is added in the `value` variable.

Instead of `keydown`, we will rather use `keyup`, which will trigger our function after input is taken care. So the comma will be embedded in the value of the field.

```javascript
<script>
let tags = [];
let value = "";
function pressed(ev){
    if(ev.key === ',') {
        tags = [...tags, value];
        value = "";
    }
};
</script>
{tags}
<input on:keyup={pressed} bind:value/> <!-- keyup instead of keydown -->
```

Now the comma no longer stays in the field, but is part of the 'value'.

I can easily remove it before adding to the list:

```javascript
value = value.replace(',','');
```

So now my code looks like :

```javascript
<script>
let tags = [];
let value = "";
function pressed(ev){
    if(ev.key === ',') {
        value = value.replace(',',''); // <-- here we remove comma
        tags = [...tags, value];
        value = "";
    }
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
```

Before continuing, I make sure that the user does not enter anything wrong in the field. For example, a single comma or an Enter with no value should have no effect, which is not the case with the current code.

So, once I remove the comma, if I have nothing left, it means I have nothing to do:

```javascript
<script>
let tags = [];
let value = "";
function pressed(ev){
    if(ev.key === ',') {
        value = value.replace(',','');
        if(value !== "") {              // <-- not empty ?
            tags = [...tags, value];    // <-- let's work...
            value = "";
        }
    }
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
```

I don't like this multi-nested level. I rather reverse the tests and exit immediately if the conditions are not met. Thus, I end up with the "good code" aligned to the left.

Demonstration:

```javascript
<script>
let tags = [];
let value = "";
function pressed(ev){
    if(ev.key !== ',') return;
    value = value.replace(',','');
    if(value === "") return;
    tags = [...tags, value];
    value = "";
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
```

Oh, I forgot I wanted to use also `Enter` as a tag separator :

```javascript
<script>
let tags = [];
let value = "";
function pressed(ev){
    if(ev.key !== ',' && ev.key !== 'Enter') return; // <-- Enter too
    value = value.replace(',','');
    tags = [...tags, value];
    value = "";
};
</script>
{tags}
<input on:keyup={pressed} bind:value/>
```

Time to take care of the tags visualization : I iterate on every saved tags with the `#each` command: 

```javascript
{#each tags as t,i}
...
{/each}
```

`#each` takes every item inside the `tags` array and makes it available in the `t` variable with its index in `i`.

Let's use this to display each tag with an X next to it. The X is used to remove the tag.

It's not a real X, it's an utf8 code that looks way more stylish 😎.

```javascript
{#each tags as t,i}
    {t} ⨉
{/each}
```

Ok, the X is not actionable yet, so I add an anchor around it with a call to a `del` function with the index of the tag to remove.

```javascript
{#each tags as t,i}
    {t} <a href="#del" on:click={()=>del(i)}>⨉</a>
{/each}
```

The `del` function just call [Splice](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice).

```javascript
function del(idx){
    tags.splice(idx,1); // <-- remove the element at index `idx`
    tags = tags;        // <-- force Svelte reactivity
}
```

The line with `tags = tags` is used to force Svelte to refresh. This is one of the rare cases where I need to explicitly indicate to Svelte that de data model has changed.

I surround each tag with a 'span' element and add a little touch of css to make it look more `taggy`:

```javascript
{#each tags as t,i}
    <span class="tag">
    {t} <a href="#del" on:click={()=>del(i)}>⨉</a>
    </span>
{/each}
<input on:keyup={pressed} bind:value/>

<style>
.tag {font-size: 0.8rem; margin-right:0.33rem; padding:0.15rem 0.25rem; border-radius:1rem; background-color: #5AD; color: white;}
.tag a {text-decoration: none; color: inherit;}
</style>
```

## Icing on the cake: suggestions!

It will be nice if I can have suggestions as I type the first characters.

![Suggestion in tags input](https://barim.us/img/svelte-tagsinput-suggest.gif)

HTML let us the possibility to add suggestions in a input field, from a predefined list: `datalist` contains the suggestions list and we use it with the `list` property inside the input field.

I define a new `tagsugg` variable with all my suggestions:

```javascript
let tagsugg = ["tag1", "tag2", "tag3"];
```

In a real project, this list can be generated from all previously saved tags in the system.

From this list, I construct the HTML `datalist`: 

```javascript
<datalist id="tag_suggestion">
    {#each tagsugg as ts}
        <option>{ts}</option>
    {/each}
</datalist>
```

Then I can reference it inside the input field with the `list` property:

```javascript
<input list="tag_suggestion" on:keyup={pressed} bind:value/>
```

That's it (for the moment) !

I now have an input text field that generates tags with a suggestion list !

A very useful evolution is to transform this code to a web component so it can be used like a HTML tag (no pun intended 😎).

You can get the complete code with comments on [this Repl Svelte page](https://svelte.dev/repl/211aabf7a4b04eae86899af96a073eba?version=4.1.1)

*This article was cross-posted from [my blog](https://barim.us/post/2023-07-26-svelte-tag-input/)*