Add "Making Customizable Components in Svelte"
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Kaan Barmore-Genç 2024-05-12 12:52:39 -05:00
parent f763cca722
commit efebfa3889
Signed by: kaan
GPG key ID: B2E280771CD62FCF
3 changed files with 129 additions and 1 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cSpell.words": ["customizability"]
}

View file

@ -1,4 +1,4 @@
<script>
<script lang="ts">
import Button from '$lib/Button.svelte';
import Spacer from '$lib/Spacer.svelte';
import { onMount } from 'svelte';

View file

@ -0,0 +1,125 @@
---
title: Making Customizable Components in Svelte
date: 2024-05-12T17:50:52+0000
tags:
- dev
- svelte
- typescript
description: Making customizable components in Svelte can be hard, but check out this post on how to use Svelte with TailwindCSS to make them possible at no performance penalty.
---
I really like [Svelte](https://svelte.dev), but it has one big limitation
in my opinion: it's hard to make customizable, reusable components.
Let me demonstrate what I mean using a React example. In React, I would
typically use one of the many ubiquitous UI libraries such as [Chakra UI](https://v2.chakra-ui.com).
Using this library, I'd write some code like this:
<iframe src="https://codesandbox.io/p/devbox/vqyf4v?view=editor+%2B+preview&file=%2FApp.tsx" title="loving-meitner-vqyf4v" />
Svelte's CSS selectors don't apply to custom components, so you can't just
write CSS targeting Unless you want to add inline styles to your components
--which have limitations of their own such as no media queries-- you can't
really do something like this in Svelte though.
```svelte
<script>
import MyButton from './MyButton.svelte';
</script>
<MyButton>Hello!</MyButton>
<style>
/* Does not work! */
MyButton {
background-color: #5e548e;
}
</style>
```
I mean, that's a bit misleading. You can do what UI libraries like Chakra do
under the hood, which is using CSS-in-JS to dynamically generate your CSS at
runtime. This comes at the [cost of worse performance and more complexity](https://dev.to/srmagura/why-were-breaking-up-wiht-css-in-js-4g9b) though,
so it's not a silver bullet.
Using [TailwindCSS](https://tailwindcss.com) though, you can get around this
issue. TailwindCSS uses a compilation step that reads through the `class`es in
your HTML (or svelte) files, and generates CSS to match the utility classes you
used. This is great for us because TailwindCSS doesn't really care if it's a
regular HTML component or a custom component where we write the classes. So you
can do something like this:
```svelte
<!-- In MyButton.svelte -->
<script>
export let className = '';
</script>
<button class={className}><slot /></button>
<!-- In App.svelte -->
<script>
import MyButton from './MyButton.svelte';
</script>
<!-- This does work! -->
<MyButton className="bg-indigo-900">Hello!</MyButton>
```
Okay great, but, my custom component basically does nothing right now. I haven't
actually customized anything beyond what a regular button is like. Can we do
that? Yes! We just need to join the "default" classes together with the classes
being passed in. TailwindCSS understands this.
```svelte
<script lang="ts">
export let className = '';
/** Combine classes together.
*
* Also allows falsy values, so you can do things like
* clsx(errorCondition ?? "color-error")
*/
function clsx(...args: (string | undefined | null | false)[]) {
return args.filter((arg) => !!arg).join(' ');
}
</script>
<!-- TailwindCSS understands this, and will generate the correct classes -->
<button class={clsx('text-white min-w-32 bg-blue-900', className)}><slot /></button>
```
I want to do one final improvement though. While we can do `<button class="...">` for regular HTML components, our custom components have to use
`<MyButton className="...">`. This is a bit annoying, because you have to switch between the two within your codebase.
Thankfully, Svelte comes to our rescue here and lets us rename exported properties. We can do that like this:
```ts
let className = '';
export { className as class };
```
And then you'll be able to do `<MyButton class="...">` as well!
Let's put all of this together in one example:
<iframe src="https://codesandbox.io/p/devbox/practical-banach-lphctd?file=%2Fsrc%2Flib%2FMyButton.svelte&embed=1" />
Combined with a TailwindCSS component library like
[DaisyUI](https://daisyui.com), this makes for a really amazing developer
experience that doesn't sacrifice performance or maintainability.
<style lang="scss">
@import '../../vars';
$extra-width: min(50%, 100vw - min($size-container, 100vw));
$height: 800px;
iframe {
box-shadow: $shadow-medium;
width: calc(100% + $extra-width);
max-width: 100vw;
margin-left: calc($extra-width / -2);
border: 0;
height: $height;
}
</style>