Enhance!
- Paolo Ricciuti Senior Software Engineer - Mainmatter
One of the key features of SvelteKit is how much is focused on progressive enhancement. It's easy to build a website that works without Javascript and get's 100 times better with it.
That said there are limitations, mainly based on the limitation of "The platform". What if you want to use a modal? Or a range slider? Those things require the usage of Javascript and thus if you want to be really keen on progressive enhancement you might find yourself avoiding those patterns in the name of the user-without-javascript experience.
But there are ways around this and this talk aim to help you navigate around the world of progressive enhancement without sacrificing the experience of the users that do have Javascript.
Transcript
[MUSIC PLAYING]
[MUSIC PLAYING]
[MUSIC PLAYING]
[MUSIC PLAYING]
[MUSIC PLAYING]
This is the classic situation in a TV series, right?
The police need to catch the bad guy, but they only have a blurred frame from the CCTV.
And so the main one asks the tech guy to zoom in and then hands.
And this is not something that you might actually ever happen to you in real life.
But if you work with me, it might happen that I tell you to announce something.
Now, before you move on with your life, asking yourself why this weirdo like the word announce so much,
allow me to introduce myself.
My name is Paolo Ricciuti, and I come from Campobasso, Italy, the land of pizza.
You can find me as Paolo Ricciuti on Twitter.
I should say X, but it's Twitter.
You can find me as Paolo Ricciuti on GitHub.
I'm a Svelte ambassador, which means that I am basically don't shut up about Svelte enough,
but the maintainers decide to make it official.
And I'm a full stack software engineer at McMatter, which is very cool because I get to work with Svelte,
but also I get to teach Svelte because we are running a workshop.
You can scan the QR code if you want, but also because we do what's called team augmentation,
which means that basically we empower our clients to learn Svelte so that they can maintain the project themselves.
And the reason why I might tell to you announce is because I'm very keen on progressive announcement.
So what progressive announcement is?
This is from Wikipedia.
Progressive announcement is a strategy in web design that puts emphasis on the web content first,
allowing everyone to access the basic content and functionality of a web page,
whilst users with additional browser features or faster internet access receive the announced version instead.
Now, there are a couple of points that I want to point out.
First, everyone to access the basic content.
This is very, very important.
You want everyone in the world to access your website.
This is very, very important for e-commerce, for example,
but in general, whichever kind of website you are building, you want the most people to access it, right?
So even one with low internet connection that lives in poor countries,
that are under the tube so there's no signal,
that are behind a firewall that blocks JavaScript.
So all of these people need to access your website.
And the ones that have faster internet connection,
that have data available that are not behind a firewall,
they will get an announced version instead.
And this is very important because this tells you something.
It's not important to serve the exact same experience
bought from the ones that are not using JavaScript and the ones that are using JavaScript.
Now, a small spoiler ahead, a small thing I want to advise you.
I will be using MeltUI for some of those examples.
MeltUI is a very cool component library for Svelte.
It's a headless component library, so they just provide you with the behavior
and you can style your components however you want.
But one important bit is that the reason why I'm using MeltUI is not because it's bad,
but just because they are providing you with components that do not exist in HTML.
So they need to use JavaScript to actually make it work.
And so this means that, I mean, out of the box, they don't work without JavaScript.
And in this talk, we will see ways to hopefully make them work.
So let's go ahead and start with the very first example.
This is a very simple page, right?
You have a form with an input auto-complete, a button to submit the form.
The form is actually using the get method, so it's basically, it will update the URL,
the upload function will run, and then, I mean, if what we are searching,
like this is just to get if we are actually searching something or not.
If we found something, we show the title and the thumbnail.
Otherwise, we show no video found.
And the input auto-complete is very, very simple.
You have a list of suggestions, and this function, fetch suggestion,
you just go ahead and fetch the auto-complete with the query,
and return the JSON, and set the value, like the value by default is void.
And on input, you just fetch the suggestions.
And down there, there is this URL, and you show the list of suggestions.
Obviously, this can be done better.
You should handle like race condition and all this stuff,
but that's not the scope of this talk.
It's just to make a point, right?
So, let's see how does this look.
Now, as you can see, we have two browser window here,
and this is the load function that is running, that I've told you before.
And the reason why I'm showing you this is because there is an error here.
I mean, it's not exactly an error, but this doesn't help with Progressive Enhancement.
And you will see a lot of these browser windows inside this presentation.
And I want to point out that these are literally the same page.
There are two iframes, and this one has JavaScript enabled,
this one has JavaScript disabled.
So, this is literally the same page.
Let's see how this input auto-complete works.
I can type here, and if I type "jelf", for example,
I get this "jelf rich svel transition accessibility".
I click here, it will get auto-complete,
and I search, and I get this very wonderful presentation from Jelf
on Svel Transition and Accessibility from Svelte Fold Summit.
Now, if I try to do the same thing here, as I've said, JavaScript is disabled.
And this means that this input auto-complete cannot work.
I have to rely on the basic input.
But if I try to search, no video found.
Why? Well, because inside my load function,
I assumed that everyone would have used my auto-complete.
And so I just basically search from the Svelte Summit talk
with a triple equal.
So, I just tell, okay, if the title is equal to the search,
just go ahead and show the show.
Otherwise, return undefined null.
So, one simple way to fix this, it's this.
Object title includes search.
This is very, very, it's a very simple fix, but it works.
Now, when I have JavaScript, I can use the accessibility.
I can use my auto-complete, and I search, and I get the talk.
But the user that doesn't have JavaScript
because maybe they are behind a firewall
or because they don't have faster internet, so it's still downloading,
and I search Jelf, and I search, I still get the same result.
So, this tells us a couple of things.
First, whenever possible, try to use HTML elements
that are already capable of sending the information to the server,
like form, button, link, input, right?
So, those are designed to send information to the server,
and you should use them whenever possible.
So, in this case, we were already using an input.
We were just announcing the experience of the input
to the user that has JavaScript.
Second, when you receive data, always assume the form or the link
has been submitted without JavaScript, and act accordingly.
This is very important because if you assume
that the one that uses your application does not have JavaScript,
you can modify your server-side code to allow for progressive enhancement.
Let's move to another example.
This is another very normal and easy example, right?
So, I have this form with this data's belt kit keep focus,
which means that if there is JavaScript enabled,
when you submit the form, it will not lose focus on the input.
And then you have this input with a name of search,
and on input we are just requesting the submit of the form.
This means that whenever we type, we get auto-submission,
the URL will update, the load function will update,
and this list of ambassadors will update.
And so, basically, we can filter them on the server-side.
So, let's see how it works.
Now, if I try to search Thomas, I get Thomas,
Enrico, I get Enrico,
Kev, I get Kev.
So, this is very cool, right?
But this has a huge problem without JavaScript,
because if I try to search Thomas,
obviously this on input will never run,
because we don't have JavaScript.
And what's worse is that we cannot submit this form.
I mean, in this very case,
given that there is just a single input,
the browser helps you, and if you tap enter,
it will actually submit the form.
But this is only true for one input,
so if there was another input, it would have not worked,
but also the user would have no way
of sending this information to the server.
So, they basically could not use your website.
The fix to this is very simple.
You just add a button.
So, if you add a button,
you can style it however you want, obviously,
and this will allow the user to submit the form.
So, inside this part, if I have JavaScript,
I can search for Thomas.
I don't have to use the button, but I can if I want.
But when I don't have JavaScript,
I can search and I can tap the button
to get the result that I want.
So, this also teaches us a couple of things.
First, always provide a fallback
to submit a form without JavaScript.
Don't assume your user will have JavaScript.
So, even if it's, I mean, controversial,
always provide a button inside a form.
And also, don't be afraid of changing the design
if that leads to better UX.
Like, in this case, the original design
didn't feature a button,
but it's better for our users.
So, you should fight for your users
to have a better experience.
And this is very important
when you are not the one making the design.
I mean, we work with designers,
so sometimes it might happen
that the designer just didn't think about the user
because maybe they tell,
"Oh, I mean, a button is bad, it's ugly."
So, fight with your designers
to allow your users to use your platform.
Let's move to the next example, right?
Here we have a very cool form.
We have a select,
which contains all the tutorial
from learn.svelte.dev.
And it's a form method post,
so we can submit this form
and we get the tutorial back
inside the form object.
And if we have the tutorial back,
we say, "Visit the tutorial,"
and we get the link for the tutorial, right?
Very simple.
Now, this is a multi-UI select.
And as you can see, it's very good.
It's styled different.
It provides extra information,
like what part of the tutorial is there.
And this is just a probably silly example
because I designed it
and I kind of suck at design.
But, I mean, you can imagine that, for example,
you are selecting a country
and you want to show the flag
and you are.
Now, there are ways to do that in HTML,
in raw HTML that are coming,
but, I mean, it's not supported yet.
And also, this is a combo box,
so I can actually literally search for something.
So I can say, for example, "await"
and I search for "await."
So, I mean, it's very cool, right?
And it works, but it has a huge problem.
Again, without JavaScript,
this can't exist.
So, yeah, this is an input,
but as long as I click here,
I mean, I don't get anything
and I cannot select anything
and I cannot search anything, right?
So, it's a very bad user experience.
So, how can we fix this?
What we want is that inside our select,
we are using the meltSelect
and passing the tutorials as options
and with the name of tutorial.
We want that if there's no JavaScript,
I want a normal select, right?
So, this can be a way of thinking.
How can we achieve this?
Now, there are ways to style things differently
when there is JavaScript and when there's not.
One way is with actions.
So, an action is belt,
is something that you can attach to an element.
And in this case, what I'm doing is that
I'm creating this action, removeClass.
And this removeClass action
take a node and the class to remove
and it does node.classList.remove to remove.
Very, very easy, right?
Then you create this div,
you initialize it with a class of Node.js
and then you use removeClass Node.js.
You then can style this Node.js
with an opacity of 0.2, for example.
You could say display none, for example.
And this means that when JavaScript kicks in,
this action will run,
will remove the class,
and it will change.
Very easy, right?
So, as you can see up there,
the style in the JS word has changed
and the style in the non-JS word
is still opacity of 0.2.
But this has a problem.
The problem is that if you have two inputs,
like the meltUISelect and the normalSelect
inside a form with the same name,
even if one is display none,
it will still submit.
So, on the server, we will still get
some browsers and bot information,
so you get two values.
Some others just override the previous one.
So, this is very risky and you should not do it.
What do we want is that we want that
when there's no JavaScript,
we just show the single normalSelect.
When there's JavaScript,
we swap that with the new meltUISelect.
And the way we can do this
is with a very, very simple component.
This is literally just these five lines of code.
I call it no-ssr.
And this basically allows you
to show nothing if during ssr.
So, if you wrap something
inside this no-ssr component,
it will not show up during ssr.
And you can also provide a fallback,
which is very useful in this case.
And how it works is that it uses
a block in Svelte
with an already resolved premise.
So, this will resolve
as soon as JavaScript kicks in.
I did some experiment and this is the fastest.
It's the first thing that actually changes
when JavaScript kicks in.
So, basically, this will resolve
as soon as JavaScript kicks in.
And this means that during server-side rendering,
this will not resolve because JavaScript
will not be able to do that.
And the fallback will be shown.
But as soon as JavaScript kicks in,
the other slot, the default slot,
will be shown.
So, how can we use this?
We can import the melt-select.
We can import the normal-select.
We can import the no-ssr component
and the tutorials.
And what I say is that inside this no-ssr,
I put the melt-select as the default.
This will not be rendered during server-side rendering
and it will be rendered when JavaScript kicks in.
And I add a slot of fallback to this normal-select
and this means that this will be rendered
during server-side rendering.
The result is this.
Can you spot the difference?
Personally, I cannot.
They are styled the same,
but now the very cool thing is that
when I click here, I get this very cool solution
with MeltUI and I can click and I can search
and I can submit the form and it works.
But when the user has no JavaScript,
so even for the first millisecond
there is no JavaScript,
this is a normal-select.
And I mean, it's uglier.
You cannot search, you cannot style
these things differently,
but at least the user can still use your website.
Very cool, right?
So, with this kind of technique,
there is also another thing that you might think
it's impossible to actually make it work.
And this is a dialog.
A dialog is a very weird piece of information
with weird pieces of components
because we actually use dialogs a lot,
but they are completely reliant on JavaScript.
So, I can open the dialog and I get my content.
Obviously, this is a silly dialog
and I can close the dialog.
Very cool.
But, I mean, it's a problem
because without JavaScript it doesn't work.
So, obviously, you should also think about
what do you put inside a dialog
because if you put something super key,
super important,
you should probably find a way
to not put it in a dialog.
But, I mean, if you want to show a dialog,
it's fine to do something.
And what you can do is actually
use the NoSSR component
and show a link to another root
styled exactly like the button.
So, this is basically how we are doing it.
We are creating the dialog with MeltUI
and with MeltUI you are spreading
this trigger on a button
and this is basically what allows MeltUI
to open the dialog with JavaScript.
But, I mean, again, this doesn't work
without JavaScript.
So, what you do is you wrap everything,
like the whole dialog,
inside this NoSSR component
so nothing gets rendered
until JavaScript is available
and you, as a fallback,
you render a link to another root.
And, I mean, this is uglier probably
but obviously look at this beautiful,
beautiful dialog.
But at least the user
that does not have JavaScript
can click and see his content.
Obviously, again, you have to be aware
of the fact that it's changing a root
so you probably will lose information.
Let's say that you just want to
signal something to the user
without having him lose
all of your data in a form, for example.
You should not use it like this way
but there are ways around this
to make it work.
This is just a silly example, obviously.
So, another important lesson.
If you are losing, just change the rules.
Right?
So, in this case, I mean,
you literally cannot do anything
with a dialog.
So, there's not much you can do
unless you change the rule.
So, you want a dialog?
I mean, if you have no JavaScript
or a link, it's okay.
You will end up seeing the content
of the dialog anyway.
Let's go ahead with another example.
And this is a very complex example.
It's the most complex example
that we have here but it's also something
that I wanted to show you
because it also showcases
how you can fix something
very, very easily.
So, this is a very normal use case, right?
You have the layout as belt
and inside your layout, at the bottom,
you have this form
to subscribe to your newsletter
because your newsletter is awesome, obviously.
And, I mean, this is shown basically
in every page, right?
So, every page you go,
you see this "Join my newsletter"
at the bottom.
And you can type in
"paolo.ricciuti@mainmatter.com"
and you join and you get this alert.
Obviously, don't use alert.
Don't use a normal alert box
because alerts are being deprecated.
But it's cool, right?
From whichever page I'm on,
I can actually just subscribe
to my very, very good newsletter.
Right?
Now, the reason, the way it works
is that we are using "henance"
which is a function provided by Svelte itself
that, when JavaScript is enabled,
basically makes this form act as a normal fetch.
And, in this case,
we are sending this information
to this "subscribe" route that we have here.
Inside this "subscribe" route,
we have a "+page.server.yes"
that exports an action, a default action,
to actually do your subscription.
You store the email in the database.
You probably sell it to some hacker.
And what you can do with "usehenance",
you can pass a function.
And this function will run
before the form actually gets submitted.
And you can return a function.
And this will be run when the action finishes.
So, at the end of the action,
you get a result.type,
which might be success
if the user actually has successfully submitted.
Or, if you throw an error here,
you will get a type of error.
But what we can do is that, if type is success,
you show the alert "subscribe".
And you can call this update function
to reset the form,
to clear the input to update the store, etc.
This, obviously, has a problem
because it doesn't work with JavaScript.
And the reason why it doesn't work
is because this is a very single bit of functionality
that differs if you use "usehenance".
And this was made specifically for this very use case
because, by default, the form in HTML,
if you are pointing to this "subscribe" page,
when you submit, it will actually try to navigate to "subscribe".
So, if I do "paolo.ricciuti@mainmatter.com"
and I submit, I will get a 500 error.
Why is that?
I mean, in this case, the action actually runs fine.
But, when there's no JavaScript enabled,
so this doesn't kick in,
what happens is that
the browser is actually navigating to "/subscribe".
And, in this case, we don't have a page.
So, how do we fix this?
We just add a "/subscribe + space.spelt"
"Thank you for subscribing!"
And now, when I click "paolo.ricciuti@mainmatter.com",
I join.
"Thanks for subscribing!"
So, progressive enhancement is an art in itself.
So, there's no pre-made solution.
So, go ahead, experiment, mess around,
stay angry and stay foolish.
I mean, no.
Find a way to get your content to your users.
But, most importantly,
thanks for watching.
[BLANK_AUDIO]