Svelte 3 has a small API surface and allows you to create components as simple as you want. You can also create complex components with a bit more effort. Reusable components is one of the goal’s of all major frontend framework and Svelte is no stranger to this. When I talk about reusable components I mean components that you can define what action it should take and how it looks.

So these are concepts that other frameworks highlight very well and have tools to help with, in Svelte we don’t really need tools as such but we need to have a better understanding of how this UI framework works. In this post we will create a simple button component, then we will build on that to make it a reusable component.

A component can be as simple as just some HTML in a .svelte file.

HTML

DefaultButton.svelte

<button>
	Hello World
</button>

This is the simplest form of a Svelte component, but this isn’t really useful since it’s not performing an action. So let’s add an action to this button by adding a on:click directive to our code with an inline function.

<button on:click={() => alert('Life has never Svelte better')}>
	Hello World
</button>

CSS

How about adding some styling to this button to give it a bit of swag (is this still a word people use?). Because Svelte’s philosophy is to build on the knowledge you already have, you can write CSS in a regular <style> tag.

<button on:click={() => alert('Life has never Svelte better')}>
	Hello World
</button>

<style>
	button {
		color: #fff;
		background-color: #333;
		border-radius: 5px;
		padding: 8px;
		border: none;
	}
</style>

Now we have a styled button, but you might be asking “Won’t the button style affect all buttons on a page if we should use this button component elsewhere?”, no it won’t, as styles inside of components don’t leak (in other words, styles are scoped) unless you use the :global modifier before the name of the styling. We will use this :global modifier later on.

Our simple component is done, now how do we allow someone to use this DefaultButton component while being allowed to change its behaviour and look without actually editing the existing DefaultButton component.

We can start with the look of the button. We will create a new component called Twitter and inside of this component, we will import our original DefaultButton component.

Twitter.svelte

<DefaultButton />

<script>
	import DefaultButton from './DefaultButton.svelte';
</script>

Whoa, hold up, what’s this <script> tag doing here and why does it have an import inside of it? OK the <script> is yet again something you might already be familiar with, but the import might be new to you, let’s just say this is how Svelte loads its component into a new file.

Let’s try to customise some of the CSS to make this button look more Twitter like.

Twitter.svelte

<DefaultButton />

<script>
  import DefaultButton from './DefaultButton.svelte';
</script>

<style>
    button {
        background-color: #1DA1F2;
    }
</style>

Notice, this had no effect at all and the Svelte compiler will tell you that there is an unused CSS selector in the code and this is because styles are scoped to their component. Inside of this component, there is no button HTML element for this CSS selector to apply to. Remember I mentioned the :global modifier earlier, let’s make use of it here.

<span class="twitter">
    <DefaultButton />
</span>

<script>
  import DefaultButton from './DefaultButton.svelte';
</script>

<style>
    .twitter :global(button) {
        background-color: #1DA1F2;
    }
</style>

There are some subtle differences in this code compared to what we had previously, a <span> tag has been added with the class name of twitter. This is to help with specificity (yep, this is a word apparently) when attaching a style to the button. Now that we have our twitter class name, we can use it as part of our selector before applying the :global modifier on the button. Now, this style won’t be truly global, it will still be scoped to this component because Svelte will attach its own svelte-* class to each HTML element generated.

Using props for content

But wait, our buttons are still saying Hello World. Surely the Twitter button should say Twitter and so on for the likes of any variation of this button while keeping the default of Hello World if we don’t change the text in the new button component we create.

We could create a prop on the DefaultButton component and use the value as the placeholder name.

DefaultButton.svelte

<button on:click={() => alert('Life has never Svelte better')}>
	{name}
</button>

<script>
	export let name = 'Hello World';
</script>

<style>
	button {
		color: #fff;
		background-color: #333;
		border-radius: 5px;
		padding: 8px;
		border: none;
	}
</style>

Then to use this we will need to update the Twitter component.

Twitter.svelte

<span class="twitter">
    <DefaultButton name="Twitter" />
</span>

<script>
  import DefaultButton from './DefaultButton.svelte';
</script>

<style>
    .twitter :global(button) {
        background-color: #1DA1F2;
    }
</style>

But this will make the content of the button be constrained to a limited amount of data and the content would have to be in string format to use it.

Using slots for content

Svelte’s has an answer to this, a thing called slots, think of these as placeholder elements that can be replaced by just passing content inside of the component’s tags.

DefaultButton.svelte

<button on:click={() => alert('Life has never Svelte better')}>
	<slot>Hello World</slot>
</button>

<style>
	button {
		color: #fff;
		background-color: #333;
		border-radius: 5px;
		padding: 8px;
		border: none;
	}
</style>

With our code now using a slot, we can add content directly inside of our DefaultButton tags whenever we use it inside of another component.

Twitter.svelte

<span class="twitter">
    <DefaultButton>Twitter</DefaultButton>
</span>

<script>
  import DefaultButton from './DefaultButton.svelte';
</script>

<style>
    .twitter :global(button) {
        background-color: #1DA1F2;
    }
</style>

And now we can put any type of HTML content inside of the tag.

Twitter.svelte

<span class="twitter">
    <DefaultButton>
        <svg height="10" viewBox="328 355 335 276" xmlns="http://www.w3.org/2000/svg"><path d="M630 425a195 195 0 01-299 175 142 142 0 0097-30 70 70 0 01-58-47 70 70 0 0031-2 70 70 0 01-57-66 70 70 0 0028 5 70 70 0 01-18-90 195 195 0 00141 72 67 67 0 01116-62 117 117 0 0043-17 65 65 0 01-31 38 117 117 0 0039-11 65 65 0 01-32 35z" fill="#fff"/></svg>
        Twitter
    </DefaultButton>
</span>

<script>
  import DefaultButton from './DefaultButton.svelte';
</script>

<style>
    .twitter :global(button) {
        background-color: #1DA1F2;
    }
</style>

The last thing you will notice is that when we click the button we are getting the same message from the underlying DefaultButton component. But to gain full reusability, we need to also control the action that takes place when the button is clicked. We can achieve this two different ways, either by using an event dispatcher or a prop on the component. I will be using the prop method for this example.

Similar to what we did earlier when we created the name prop in the DefaultButton component, here we will create a buttonAction prop for the consumer of this button to use, but we will also leave a default behaviour should the user decide to not set this prop.

DefaultButton.svelte

<button on:click={buttonAction}>
  <slot>Hello World</slot>
</button>

<script>
    export let buttonAction = () => alert('Life has never Svelte better');
</script>


<style>
	button {
		color: #fff;
		background-color: #333;
		border-radius: 5px;
		padding: 8px;
		border: none;
	}
</style>

We can now make use of this in our Twitter component by passing the action as a prop to the component.

Twitter.svelte

<span class="twitter">
    <DefaultButton buttonClick={() => alert('I am going to Twitter')}>
        <svg height="10" viewBox="328 355 335 276" xmlns="http://www.w3.org/2000/svg"><path d="M630 425a195 195 0 01-299 175 142 142 0 0097-30 70 70 0 01-58-47 70 70 0 0031-2 70 70 0 01-57-66 70 70 0 0028 5 70 70 0 01-18-90 195 195 0 00141 72 67 67 0 01116-62 117 117 0 0043-17 65 65 0 01-31 38 117 117 0 0039-11 65 65 0 01-32 35z" fill="#fff"/></svg>
        Twitter
    </DefaultButton>
</span>

<script>
  import DefaultButton from './DefaultButton.svelte';
</script>

<style>
    .twitter :global(button) {
        background-color: #1DA1F2;
    }
</style>

Even if our Twitter component didn’t contain the buttonClick we would still get the default alert that was set up inside our DefaultButton component.

Now we have a fully reusable DefaultButton component that we can reuse to create new components. You could go ahead and create a Facebook component and customise it as you see fit without affecting any of the existing button components you already created.