Add "Making Customizable Components in Svelte"
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
f763cca722
commit
efebfa3889
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cSpell.words": ["customizability"]
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import Button from '$lib/Button.svelte';
|
||||
import Spacer from '$lib/Spacer.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
|
125
src/routes/posts/2024.05.12.svelte-customizable-components.md
Normal file
125
src/routes/posts/2024.05.12.svelte-customizable-components.md
Normal 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>
|
Loading…
Reference in a new issue