The Svelte transition API provides a first-class way to animate your components when they enter or leave the document, including custom Svelte transitions. By default, the transition directive uses CSS animations, which generally offer better performance and allow the browser’s main thread to remain unblocked. The API is as simple as this: <element transition:transitionFunction />. You can also specify in or out directives which are uni-directional transitions, only running when the element is mounted or unmounted.
Svelte offers a runtime svelte/transition package that ships with seven prepackaged Svelte transition functions, all of which can be dropped in and tweaked to your heart’s desire. Pairing this with the svelte/easing package, allows for a wide swath of interactions, without writing any of the transition code yourself. Play around with different transitions and easing functions to get a feel for what is possible.
Looking for instructions on how to get started with Svelte? We have a solid overview for you to check out.
The Svelte Custom Transition API
If you need even more control than what the Svelte Transition API offers out of the box, Svelte permits you to specify your own custom transition function, as long as you adhere to a few conventions. From the docs, here’s what the custom transition API looks like:
Let’s break it down. A transition function takes a reference to the DOM node where the transition directive is used and returns an object with some parameters that control the animation and, most importantly, a css or tick function.
Both the css and tick functions take two parameters called (t, u) by convention. t is a decimal number that travels from 0.00 to 1.00 while the element is entering the DOM and from 1.00 back to 0.00 when the element is leaving. The u parameter is the inverse of t or 1 - t at any given moment. For example, if you return a string of transform: scale($t), your element would smoothly animate from 0 to 1 on enter, and vice versa on exit.
These concepts may seem a bit abstract, so let’s solidify them by building our own custom Svelte transition!
Building your first custom Svelte transition
First, let’s set up some boilerplate that allows us to toggle an element’s existence in the DOM using a Svelte #if block. Remember, Svelte transitions only run when an element is actually leaving or entering the DOM.
You should be able to toggle the checkbox and see our element starkly appear and disappear in place.
Next, let’s set up our custom Svelte transition function and get it wired up to our element.
let showing = true
// Custom transition function
<input id="showing" type="checkbox" bind:checked=showing />
<h1 transition:whoosh>Hello custom transition!</h1>
Now, if you toggle the checkbox, you will see the <h1> element logged to the console. This proves we have the custom transition connected properly! We won’t actually use the DOM node in our example, but it’s often useful to have access to the element to reference its current styles or dimensions.
For our element to do any animation at all, we need to return an object that contains a css (or tick) function. Let’s have our css function return a single line of CSS that scales our element. We’ll also return a duration property that controls how long the animation takes.
css: () => `transform: scale(.5)`
let showing = true
<!-- markup -->
We’ve got something moving! You will notice our element jumps straight to .5 scale when toggling the checkbox. This is something, but it would feel much better if it smoothly transitioned. That’s where the (t, u) parameters come in.
css: (t) => `transform: scale($t)`
let showing = true
<!-- markup -->
Now we are talking! Remember, t rolls smoothly from 0.00 to 1.00 when an element enters, and vice versa when it leaves. This allows us to achieve the smooth effect we want. In fact, what we just wrote is essentially the built-in scale transition from the svelte/transition package.
Let’s get a little bit fancier. To live up to our custom Svelte transition’s namesake, swoop, let’s add a translateX to our transform, so that our element zooms in and out from the side.
I want to challenge you to attempt the implementation first before we continue. Trust me, it will be fun! Assume that we want to translate to 100% when the element is leaving and back to 0% when it enters.
It’s okay if you have something different! Let me break down what I did.
The key thing here is the usage of the second parameter in the css function. If we think about our animation while the element is entering the screen, we want to end up at scale(1) translateX(0%), so we can’t use unaltered t for both the scale and the transform. This is the convenience behind the u parameter — it is the inverse of t at any given moment, so we know it will be 0 when t is 1! I then multiplied u by 100 to get the percentage value and tacked on the % sign at the end.
Learning the interplay between t and u is an important piece of the custom transition puzzle in Svelte. These two parameters enable a world of dynamism for your animations; they can be divided, multiplied, twisted, or contorted into whatever needs you have.
Let’s slap my favorite svelte/easing function on our transition and call it a day:
Congratulations! You can now build a custom Svelte transition function. We have only scratched the surface of what is possible but I hope you feel equipped with the tools to explore even further. I would highly recommend reading the docs and going through the official tutorial to gain even more familiarity.
How to Build Your First Custom Svelte Transition originally published on CSS-Tricks. You should get the newsletter and become a supporter.
Handle updating your components based on whether you’ve joined a Daily call or not
Manage your Daily Prebuilt call with a custom control panel
If you want to test the completed version of the demo first, check out the deployed version.
What exactly is Svelte?
Svelte is an open-source frontend component framework that can be used as an alternative to other frontend frameworks, like React or Vue.
“Svelte runs at build time, converting your components into highly efficient imperative code that surgically updates the DOM. As a result, you’re able to write ambitious applications with excellent performance characteristics.”
Who is this tutorial for?
Since this is a Svelte tutorial, the following content will be most helpful for anyone already familiar with Svelte. In case you’re not, we’ll do our best to explain what is unique to Svelte.
Thankfully, Svelte also has amazing documentation and interactive tutorials to learn the basics, and we highly recommend giving those a read!
Note: The demo README includes additional instructions for creating new Daily rooms locally via the app.
Once you have the app running locally, navigate to http://localhost:5000 in your browser of choice, and you should see the home page.
App.svelte: Determining which view to show
Our app is going to have two possible views:
Our home page, which includes a form to join a call
The call UI, which includes the Daily Prebuilt embed and our custom call controls
We know we’ll need some logic to determine which view should show. To determine this, let’s take a look at our parent App component. App will handle deciding whether the Home or Call component is rendered.
// App.svelte<script>importCallfrom"./screens/Call.svelte";importHeaderfrom"./components/Header.svelte";importHomefrom"./screens/Home.svelte";letcurrentScreen="home";// || 'call'leturl;letuserName;consthandleJoinCall=(detail)=>currentScreen="call";// set component vars with form submission valuesurl=detail.url;userName=detail.name;// save in local storagelocalStorage.setItem("svelte-prebuilt-url",url);localStorage.setItem("svelte-prebuilt-name",userName);;consthandleLeaveCall=()=>currentScreen="home";;</script>
Let’s step through this:
In the script tag, we start by importing the components we’ll be using (Call, Home, and Header)
Then, we declare variables that will be used in this component but are assigned later on.
Next, we define handleJoinCall, which we’ll describe in more detail below. In short, it sets our currentScreen variable to call.
We then define handleLeaveCall, which simply resets currentScreen to home.
Lastly, we import our daily-js script tag to make the daily-js library available to the rest of the app.
Now let’s specifically look at the markdown in App to see how we render our components:
Here, we have a native HTML <form> element with the submit handler using goToCall:
Note: This is not a custom event since forms have a native submit event.
For example, the username input value will be bound to the variable name, which is declared at the top of the file:
In goToCall, we first prevent the form from refreshing the page with e.preventDefault().
Then we use our dispatch method to forward the submit event to our App component. Both name and url (our variables bound to the inputs) are passed as options to make those values available to App, as well.
If you recall from App, the Home component has an event listener on it for submit, which calls the handleJoinCall method.
<Home on:submit=handleJoinCall />
When our dispatched submit event registers in App, it will call handleJoinCall.
// App.svelteconsthandleJoinCall=(detail)=>currentScreen="call";// set component vars with form submission valuesurl=detail.url;userName=detail.name;// save in local storagelocalStorage.setItem("svelte-prebuilt-url",url);localStorage.setItem("svelte-prebuilt-name",userName);error=null;;
In handleJoinCall we update currentScreen to equal call. This will cause the Call component to show instead of Home. We then set our url and userName variables to the values passed from the form and save them in local storage, as well.
Now that all the Home form’s information is shared with App — who also shares it with Call — we can move on to setting up our call UI.
Call on line 1, please
So far we’ve set up our App component and our Home screen. Now let’s build our Daily call in Call.svelte.
Let’s start with the Call component’s HTML markdown this time.
// Call.svelteimportonMountfrom"svelte";onMount(()=>// assume if the Call component is showing, we should joininitializeDaily(););
We know the Call component mounts when the form is submitted, so we want to initialize the call as soon as Call renders. We can do this by calling initializeDaily on mount.
// Call.svelteconstinitializeDaily=async()=>…// select container element to embed Daily iframe inconstcontainer=document.getElementById("container");// create Daily iframecallFrame=window.DailyIframe.createFrame(container,iframeStyle:IFRAME_OPTIONS,showLeaveButton:true,url,userName,);callFrame.on("joining-meeting",updateMeetingState);callFrame.on("joined-meeting",updateMeetingState);callFrame.on("left-meeting",handleLeftMeeting);callFrame.on("error",updateMeetingState);// set up interval for retrieving current network statsinterval=setInterval(()=>getNetworkStats(),5000);// let the local user join the call, which will cause// the call to be displayed in our app UIawaitcallFrame.join();;
Stepping through this initializeDaily function:
We first select our div element that will be the Daily Prebuilt iframe’s container:
And, finally, we use Daily’s join method to actually join the call. 🎉
Adding our custom control panel
As Bruce Springsteen once said, “Honey, I want the heart, I want the soul, I want control right now,” so let’s do just that and add a little more control to our app.
daily-js provides instance methods to programmatically do anything you can already do via Daily Prebuilt’s UI. This gives a bit more flexibility to how you want to set up your own app’s UI.
For example, if you want to add a big “Mute” button to your UI, you can! Let’s take a look at how.
Adding a Mute button to toggle local audio
As mentioned, our Call component has a bunch of custom event listeners added to Controls. This means all the actual Daily logic can stay contained in our Call component. The Controls component is basically just UI to dispatch the custom events.
In Controls, we have a button to mute the local user:
Your mission — should you decide to accept it — is to build a Button component in four frameworks, but, only use one button.css file!
The source code for this article is available on GitHub on the the-little-button-that-could-series branch.
We’re going to set up a tiny Yarn workspaces-based monorepo. Why? Chris actually has a nice outline of the benefits in another post. But here’s my own biased list of benefits that I feel are relevant for our little buttons endeavor:
We’re trying to build a single button component that uses just one button.css file across multiple frameworks. So, by nature, there’s some purposeful coupling going on between the various framework implementations and the single-source-of-truth CSS file. A monorepo setup provides a convenient structure that facilitates copying our single button.css component into various framework-based projects.
Let’s say the button needs a tweak — like the “focus-ring” implementation, or we screwed up the use of aria in the component templates. Ideally, we’d like to correct things in one place rather than making individual fixes in separate repositories.
We want the convenience of firing up all four button implementations at the same time for testing. As this sort of project grows, it’s safe to assume there will be more proper testing. In AgnosticUI, for example, I’m currently using Storybook and often kick off all the framework Storybooks, or run snapshot testing across the entire monorepo.
I like what Leonardo Losoviz has to say about the monorepo approach. (And it just so happens to align with with everything we’ve talked about so far.)
I believe the monorepo is particularly useful when all packages are coded in the same programming language, tightly coupled, and relying on the same tooling.
Time to dive into code — start by creating a top-level directory on the command-line to house the project and then cd into it. (Can’t think of a name? mkdir buttons && cd buttons will work fine.)
First off, let’s initialize the project:
$ yarn init
yarn init v1.22.15
question name (articles): littlebutton
question version (1.0.0):
question description: my little button project
question entry point (index.js):
question repository url:
question author (Rob Levin):
question license (MIT):
success Saved package.json
That gives us a package.json file with something like this:
"description": "my little button project",
"author": "Rob Levin",
Creating the baseline workspace
We can set the first one up with this command:
mkdir -p ./littlebutton-css
Next, we need to add the two following lines to the monorepo’s top-level package.json file so that we keep the monorepo itself private. It also declares our workspaces:
Now descend into the littlebutton-css directory. We’ll again want to generate a package.json with yarn init. Since we’ve named our directory littlebutton-css (the same as how we specified it in our workspaces in package.json) we can simply hit the Return key and accept all the prompts:
$ cd ./littlebutton-css && yarn init
yarn init v1.22.15
question name (littlebutton-css):
question version (1.0.0):
question entry point (index.js):
question repository url:
question author (Rob Levin):
question license (MIT):
success Saved package.json
At this point, the directory structure should look like this:
We’ve only created the CSS package workspace at this point as we’ll be generating our framework implementations with tools like vite which, in turn, generate a package.json and project directory for you. We will have to remember that the name we choose for these generated projects must match the name we’ve specified in the package.json for our earlier workspaces to work.
Baseline HTML & CSS
Let’s stay in the ./littlebutton-css workspace and create our simple button component using vanilla HTML and CSS files.
And, just so we have something visual to test, we can add a little color in ./css/button.css:
Now open up that index.html page in the browser. If you see an ugly generic button with hotpink text… success!
So what we just accomplished is the baseline for our button component. What we want to do now is abstract it a bit so it’s extensible for other frameworks and such. For example, what if we want to use the button in a React project? We’re going to need workspaces in our monorepo for each one. We’ll start with React, then follow suit for Vue 3, Angular, and Svelte.
We’re going to generate our React project using vite, a very lightweight and blazingly fast builder. Be forewarned that if you attempt to do this with create-react-app, there’s a very good chance you will run into conflicts later with react-scripts and conflicting webpack or Babel configurations from other frameworks, like Angular.
To get our React workspace going, let’s go back into the terminal and cd back up to the top-level directory. From there, we’ll use vite to initialize a new project — let’s call it littlebutton-react — and, of course, we’ll select react as the framework and variant at the prompts:
$ yarn create vite
yarn create v1.22.15
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
success Installed "firstname.lastname@example.org" with binaries:
✔ Project name: … littlebutton-react
✔ Select a framework: › react
✔ Select a variant: › react
Scaffolding project in /Users/roblevin/workspace/opensource/guest-posts/articles/littlebutton-react...
Done. Now run:
✨ Done in 17.90s.
We initialize the React app with these commands next:
With React installed and verified, let’s replace the contents of src/App.jsx to house our button with the following code:
Now we’re going to write a small Node script that copies our littlebutton-css/css/button.css right into our React application for us. This step is probably the most interesting one to me because it’s both magical and ugly at the same time. It’s magical because it means our React button component is truly deriving its styles from the same CSS written in the baseline project. It’s ugly because, well, we are reaching up out of one workspace and grabbing a file from another. ¯_(ツ)_/¯
Add the following little Node script to littlebutton-react/copystyles.js:
Let’s place a node command to run that in a package.json script that happens before the dev script in littlebutton-react/package.json. We’ll add a syncStyles and update the dev to call syncStyles before vite:
Now, anytime we fire up our React application with yarn dev, we’ll first be copying the CSS file over. In essence, we’re “forcing” ourselves to not diverge from the CSS package’s button.css in our React button.
But we want to also leverage CSS Modules to prevent name collisions and global CSS leakage, so we have one more step to do to get that wired up (from the same littlebutton-react directory):
Next, add the following to the new src/button.module.css file:
composes: btn from './button.css';
I find composes (also known as composition) to be one of the coolest features of CSS Modules. In a nutshell, we’re copying our HTML/CSS version of button.css over wholesale then composing from our one .btn style rule.
With that, we can go back to our src/App.jsx and import the CSS Modules styles into our React component with this:
Whew! Let’s pause and try to run our React app again:
If all went well, you should see that same generic button, but with hotpink text. Before we move on to the next framework, let’s move back up to our top-level monorepo directory and update its package.json:
Run the yarn command from the top-level directory to get the monorepo-hoisted dependencies installed.
The only change we’ve made to this package.json is a new scripts section with a single script to start the React app. By adding start:react we can now run yarn start:react from our top-level directory and it will fire up the project we just built in ./littlebutton-react without the need for cd‘ing — super convenient!
Following the steps from vite’s scaffolding docs we’ll run the following command from the monorepo’s top-level directory to initialize a Vue app:
yarn create vite littlebutton-vue --template vue
This generates scaffolding with some provided instructions to run the starter Vue app:
This should fire up a starter page in the browser with some heading like “Hello Vue 3 + Vite.” From here, we can update src/App.vue to:
:class="classes" is using Vue’s binding to call the computed classes method.
The classes method, in turn, is utilizing CSS Modules in Vue with the this.$style.btn syntax which will use styles contained in a <style module> tag.
For now, we’re hardcoding color: slateblue simply to test that things are working properly within the component. Try firing up the app again with yarn dev. If you see the button with our declared test color, then it’s working!
Now we’re going to write a Node script that copies our littlebutton-css/css/button.css into our Button.vue file similar to the one we did for the React implementation. As mentioned, this component is a SFC so we’re going to have to do this a little differently using a simple regular expression.
Add the following little Node.js script to littlebutton-vue/copystyles.js:
const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
const vue = fs.readFileSync("./src/components/Button.vue", "utf8");
// Take everything between the starting and closing style tag and replace
const styleRegex = /<style module>([sS]*?)</style>/;
let withSynchronizedStyles = vue.replace(styleRegex, `<style module>n$cssn</style>`);
fs.writeFileSync("./src/components/Button.vue", withSynchronizedStyles, "utf8");
There’s a bit more complexity in this script, but using replace to copy text between opening and closing style tags via regex isn’t too bad.
Now let’s add the following two scripts to the scripts clause in the littlebutton-vue/package.json file:
One last thing: Svelte appears to name our app: "name": "svelte-app" in the package.json. Change that to "name": "littlebutton-svelte" so it’s consistent with the workspaces name in our top-level package.json file.
Once again, we can copy our baseline littlebutton-css/css/button.css into our Button.svelte. As mentioned, this component is a SFC, so we’re going to have to do this using a regular expression. Add the following Node script to littlebutton-svelte/copystyles.js:
Now run yarn syncStyles && yarn dev. If all is good, we once again should see a button with hotpink text.
If this is starting to feel repetitive, all I have to say is welcome to my world. What I’m showing you here is essentially the same process I’ve been using to build my AgnosticUI project!
You probably know the drill by now. From the monorepo’s top-level directory, install Angular and create an Angular app. If we were creating a full-blown UI library we’d likely use ng generate library or even nx. But to keep things as straightforward as possible we’ll set up a boilerplate Angular app as follows:
npm install -g @angular/cli ### unless you already have installed
ng new littlebutton-angular ### choose no for routing and CSS
? Would you like to add Angular routing? (y/N) N
SCSS [ https://sass-lang.com/documentation/syntax#scss ]
Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]
Less [ http://lesscss.org ]
cd littlebutton-angular && ng serve --open
With the Angular setup confirmed, let’s update some files. cd littlebutton-angular, delete the src/app/app.component.spec.ts file, and add a button component in src/components/button.component.ts, like this:
import Component from '@angular/core';
export class ButtonComponent
Add the following to src/components/button.component.html:
And put this in the src/components/button.component.css file for testing:
import NgModule from '@angular/core';
import BrowserModule from '@angular/platform-browser';
import AppComponent from './app.component';
import ButtonComponent from '../components/button.component';
declarations: [AppComponent, ButtonComponent],
export class AppModule
Next, replace src/app/app.component.ts with:
import Component from '@angular/core';
export class AppComponent
Then, replace src/app/app.component.html with:
With that, let’s run yarn start and verify our button with fuchsia text renders as expected.
Again, we want to copy over the CSS from our baseline workspace. We can do that by adding this to littlebutton-angular/copystyles.js:
[…] the behavior of shadow DOM by preprocessing (and renaming) the CSS code to effectively scope the CSS to the component’s view.
This basically means we can literally copy over button.css and use it as-is.
Finally, update the package.json file by adding these two lines in the scripts section:
"start": "yarn syncStyles && ng serve",
"syncStyles": "node copystyles.js",
With that, we can now run yarn start once more and verify our button text color (which was fuchsia) is now hotpink.
What have we just done?
Let’s take a break from coding and think about the bigger picture and what we’ve just done. Basically, we’ve set up a system where any changes to our CSS package’s button.css will get copied over into all the framework implementations as a result of our copystyles.js Node scripts. Further, we’ve incorporated idiomatic conventions for each of the frameworks:
SFC for Vue and Svelte
CSS Modules for React (and Vue within the SFC <style module> setup)
ViewEncapsulation for Angular
Of course I state the obvious that these aren’t the only ways to do CSS in each of the above frameworks (e.g. CSS-in-JS is a popular choice), but they are certainly accepted practices and are working quite well for our greater goal — to have a single CSS source of truth to drive all framework implementations.
If, for example, our button was in use and our design team decided we wanted to change from 4px to 3pxborder-radius, we could update the one file, and any separate implementations would stay synced.
This is compelling if you have a polyglot team of developers that enjoy working in multiple frameworks, or, say an offshore team (that’s 3× productive in Angular) that’s being tasked to build a back-office application, but your flagship product is built in React. Or, you’re building an interim admin console and you’d love to experiment with using Vue or Svelte. You get the picture.
OK, so we have the monorepo architecture in a really good spot. But there’s a few things we can do to make it even more useful as far as the developer experience goes.
Better start scripts
Let’s move back up to our top-level monorepo directory and update its package.jsonscripts section with the following so we can kick any framework implementation without cd‘ing:
Let’s test this out! Fire up each of the four framework implementations with the new and improved start scripts and confirm the styling changes are in effect.
One CSS file update proliferated to four frameworks — pretty cool, eh!?
Set a primary mode
We’re going to add a mode prop to each of our button’s and implement primary mode next. A primary button could be any color but we’ll go with a shade of green for the background and white text. Again, in the baseline stylesheet:
There we go! Some developer conveniences and better baseline styles!
Updating each component to take a mode property
Now that we’ve added our new primary mode represented by the .btn-primary class, we want to sync the styles for all four framework implementations. So, let’s add some more package.json scripts to our top level scripts:
Running this doesn’t change anything because we haven’t applied the primary class yet, but you should at least see the CSS has been copied over if you go look at the framework’s button component CSS.
If you haven’t already, double-check that the updated CSS got copied over into littlebutton-react/src/button.css. If not, you can run yarn syncStyles. Note that if you forget to run yarn syncStyles our dev script will do this for us when we next start the application anyway:
"dev": "yarn syncStyles && vite",
For our React implementation, we additionally need to add a composed CSS Modules class in littlebutton-react/src/button.module.css that is composed from the new .btn-primary:
composes: btn-primary from './button.css';
Now we can update the markup in littlebutton-vue/src/App.vue to use the new mode prop:
Now you can yarn start:vue from the top-level directory and check for the same green button.
Let’s cd into littlebutton-svelte and verify that the styles in littlebutton-svelte/src/Button.svelte have the new .btn-primary class copied over, and yarn syncStyles if you need to. Again, the dev script will do that for us anyway on the next startup if you happen to forget.
Next, update the Svelte template to pass the mode of primary. In src/App.svelte:
import Button from './Button.svelte';
We also need to update the top of our src/Button.svelte component itself to accept the mode prop and apply the CSS Modules class:
Now we need to set up a binding to a classes getter to compute the correct classes based on if the mode was passed in to the component or not. Add this to littlebutton-angular/src/components/button.component.html (and note the binding is happening with the square brackets):
Next, we actually need to create the classes binding in our component at littlebutton-angular/src/components/button.component.ts:
import Component, Input from '@angular/core';
export class ButtonComponent undefined = undefined;
public get classes(): string
const modeClass = this.mode ? `btn-$this.mode` : '';
].filter(cl => cl.length).join(' ');
We use the Input directive to take in the mode prop, then we create a classes accessor which adds the mode class if it’s been passed in.
Fire it up and look for the green button!
If you’ve made it this far, congratulations — you’ve reached code complete! If something went awry, I’d encourage you to cross-reference the source code over at GitHub on the the-little-button-that-could-series branch. As bundlers and packages have a tendency to change abruptly, you might want to pin your package versions to the ones in this branch if you happen to experience any dependency issues.
Take a moment to go back and compare the four framework-based button component implementations we just built. They’re still small enough to quickly notice some interesting differences in how props get passed in, how we bind to props, and how CSS name collisions are prevented among other subtle differences. As I continue to add components to AgnosticUI (which supports these exact same four frameworks), I’m continually pondering which offers the best developer experience. What do you think?
If you’re the type that likes to figure things out on your own or enjoys digging in deeper, here are ideas.
The current button styles do not account for various states, like :hover. I believe that’s a good first exercise.
/* You should really implement the following states
but I will leave it as an exercise for you to
decide how to and what values to use.
/* If you elect to remove the outline, replace it
with another proper affordance and research how
to use transparent outlines to support windows
Most button libraries support many button variations for things like sizes, shapes, and colors. Try creating more than the primary mode we already have. Maybe a secondary variation? A warning or success? Maybe filled and outline? Again, you can look at AgnosticUI’s buttons page for ideas.
CSS custom properties
If you haven’t started using CSS custom properties yet, I’d strongly recommend it. You can start by having a look at AgnosticUI’s common styles. I heavily lean on custom properties in there. Here are some great articles that cover what custom properties are and how you might leverage them:
A Complete Guide to Custom Properties
A DRY Approach to Color Themes in CSS
No… not typings, but the <button> element’s type attribute. We didn’t cover that in our component but there’s an opportunity to extend the component to other use cases with valid types, like button, submit, and reset. This is pretty easy to do and will greatly improve the button’s API.
Gosh, you could do so much — add linting, convert it to Typescript, audit the accessibility, etc.
The current Svelte implementation is suffering from some pretty loose assumptions as we have no defense if the valid primary mode isn’t passed — that would produce a garbage CSS class:
mode ? `btn-$mode` : "",
You could say, “Well, .btn-garbage as a class isn’t exactly harmful.” But it’s probably a good idea to style defensively when and where possible.
There are some things you should be aware of before taking this approach further:
Positional CSS based on the structure of the markup will not work well for the CSS Modules based techniques used here.
Angular makes positional techniques even harder as it generates :host element representing each component view. This means you have these extra elements in between your template or markup structure. You’ll need to work around that.
Copying styles across workspace packages is a bit of an anti-pattern to some folks. I justify it because I believe the benefits outweigh the costs; also, when I think about how monorepos use symlinks and (not-so-failproof) hoisting, I don’t feel so bad about this approach.
You’ll have to subscribe to the decoupled techniques used here, so no CSS-in-JS.
I believe that all approaches to software development have their pros and cons and you ultimately have to decide if sharing a single CSS file across frameworks works for you or your specific project. There are certainly other ways you could do this (e.g. using littlebuttons-css as an npm package dependency) if needed.
Hopefully I’ve whet your appetite and you’re now really intrigued to create UI component libraries and/or design systems that are not tied to a particular framework. Maybe you have a better idea on how to achieve this — I’d love to hear your thoughts in the comments!
I’m sure you’ve seen the venerable TodoMVC project and how many framework implementations have been created for it. Similarly, wouldn’t it be nice to have a UI component library of primitives available for many frameworks? Open UI is making great strides to properly standardize native UI component defaults, but I believe we’ll always need to insert ourselves to some extent. Certainly, taking a good year to build a custom design system is quickly falling out of favor and companies are seriously questioning their ROI. Some sort of scaffolding is required to make the endeavor practical.
The vision of AgnosticUI is to have a relatively agnostic way to build design systems quickly that are not tied down to a particular frontend framework. If you’re compelled to get involved, the project is still very early and approachable and I’d love some help! Plus, you’re already pretty familiar with the how the project works now that you’ve gone through this tutorial!
How to Make a Component That Supports Multiple Frameworks in a Monorepo originally published on CSS-Tricks. You should get the newsletter and become a supporter.
Hello everyone kindly check out this github repo which hosts the code for my tutorial on how to use Svelte and Google’s WebRTC.
What is WebRTC?
Why I am doing this project?
I feel like svelte is new and has come with some solutions to some of the pain points in today’s front end frameworks like react.js and vue.js, and I felt inspired to implement a webRTC application using Svelte only.
If you like WebRTC and would like to learn more about or you want to support me with some code, here is the repo .
// useSession.tsimportgetStoresfrom"$app/stores";importgetas$,writablefrom"svelte/store";importloadSession,sessionDatafrom"../useLoad";exportfunctionuseSession():session:Session;data:SessionDataconstsession=$(loadSession)??($(getStores().session)asSession);constdata=sessionData.get(session);if(!data)thrownewError("Call useLoad before calls to useSession");returnsession,data,;
Now we have:
useLoad(input, ...) that needs to be called inside the load function.
useSession() which can get the current session, even within the load function.
// useProducts.tsimportbrowserfrom"$app/env";importwritablefrom"svelte/store";importdefineStorefrom"./defineStore";importuseUserfrom"./useUser";constuseProducts=defineStore(()=>null>(null);// Remove products on log out. Only execute client-side// to prevent memory leaksif(browser)user.subscribe($user=>if(!$user)products.set(null););returnproducts;);
We can see that the products store can even look up the user store!
How does this work? defineStore takes a function as a parameter. The first time the store is needed for the current session, the function is executed. The subsequent times, the cached result is returned:
One particularity about SvelteKit is that it provides a fetch function that should be used within load. The reason is so that when the same calls are executed both serverside & clientside, the cached value can be used clientside instead of repeating the call.
Maybe you noticed, but in the above useLoad, we stored input.fetch in sessionData.
And here is what it would look like inside a component:
<scriptlang="ts">importuseProductfrom"$lib/use/useProduct";constproducts,loadProducts=useProducts();constuser=useUser();$:loadProducts(),[$user]// reloads products each time user changes</script>
So that’s it! We now have a uniform way of accessing per-user global stores, whether we’re serverside or clientside, and whether we’re in a load function or inside a component.
As a bonus, we also have a uniform way of accessing the correct fetch function 😉
I recently discovered Svelte and became my first choice for personal projects and MVP/POCs.
As a backend developer, I struggled a lot with frameworks like React or Vue, looking for a productive workflow. Svelte is dead simple compared to them, in my opinion. And yet very powerful.
However, coming from Django, I still struggled on stuff like security or form validation: if I want a SPA, do I really need to use only Django Rest Framework and implement everything through REST API?
Take authentication for example: Django has built-in views for doing it in a secure way. Do I really need to re-implement it every time client-side using JWT? Looks like a waste of time, for my productivity at least.
I really like Django approach “batteries included”, since it ships with best practices and it makes me a lot more focused on the goal I want to achieve.
On the other hand, I still want the flexibility and reactivity provided by a SPA framework.
So, after multiple projects, I’d like to share in this blog post my personal take aways and show how I scaffold my projects, in order to get:
a Django website, using the powerful template system for authentication, registration and password recovery (using Django built in views)
a Svelte app served by Django, with hot reloading during development
This way helps me a lot, because it keeps the best of the three worlds without compromising productivity.
About this post
Please consider the following as my personal vademecum for setting up a Django+Svelte project quickly with basic usefules dependencies (such as whitenoise, black…) and authentication (which I haven’t found much covered online).
It is a concentrated boilerplate of my favourites tweaks on Django that made me proficient. Feel free to ignore those who don’t interests you. Any feedback is very welcome.
I’m using Svelte because is my favourite, but the approach should work for Vue and React, with obvious adjustments.
A medium Django/Python and Svelte knowledge is required.
TEMPLATES=["BACKEND":"django.template.backends.django.DjangoTemplates","DIRS":["myapp/templates"],# <-- here
and create the folder:
Django static files
As mentioned, I really love Whitenoise for serving static files directly from Django. This helps me a lot during development and deploying because I can stay within Django.
We now create a staticfiles folder inside myapp, where files will be collected when we go to production, and the folder that will contain the compiled Svelte files, that will be served as Django static files (we will see it later)
We need to configure out static configurations accordingly:
And do not forget to add Whitenoise to the list of middlewares:
MIDDLEWARE=["django.middleware.security.SecurityMiddleware","whitenoise.middleware.WhiteNoiseMiddleware",# <-- here
Finish Django configuration
Let’s complete Django configuration creating applying the migrations and starting up the server, just to be sure no warnings came up
If you want to just check that everything is ok, you can run:
DJANGO_DEBUG=ON poetry run python manage.py makemigrations
DJANGO_DEBUG=ON poetry run python manage.py migrate
DJANGO_DEBUG=ON poetry run python manage.py runserver
You should see
Watching for file changes with StatReloader
[2021-12-09 18:31:03,519][ INFO][django.utils.aut]@[ 643]$ Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
December 09, 2021 - 18:31:03
Django version 4.0, using settings 'myapp.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
So far, so good. We just instructed Django to do some useful stuff through standard configuration. Let’s add the Svelte part in order to see something.
For now, we will just expose the Svelte default app from Django.
The idea here is: we will serve the files from Django static/frontend dir and we will develop from Django (ie. localhost:8000) instead of localhost:5000 (default rollup dev server).
All we have to do is configure rollup to emit the build files in the static django folder.
Go to frontend/rollup.config.js and change the export default like this:
Modify the myapp/templates/spa/index.html using static resources from the frontend
% load static %
<!DOCTYPE html><htmllang="en"><head><metacharset='utf-8'><metaname='viewport'content='width=device-width,initial-scale=1'><title>Svelte app</title><linkrel='icon'type='image/png'href="% static 'frontend/favicon.png' %"><!-- <-- here --><linkrel='stylesheet'href="% static 'frontend/global.css' %"><!-- <-- here --><linkrel='stylesheet'href="% static 'frontend/bundle.css' %"><!-- <-- here --><script defersrc="% static 'frontend/bundle.js' %"></script><!-- <-- here --></head><body></body></html>
Add the view in the myapps/urls.py
fromdjango.contribimportadminfromdjango.urlsimportpathfrommyapp.spa.viewsimportSpaView# <-- here
urlpatterns=[path("admin/",admin.site.urls),path("",SpaView.as_view(),name="spa"),# <-- here
and finally, in order to add it in the installed apps, we need to:
classSpaConfig(AppConfig):default_auto_field="django.db.models.BigAutoField"name="myapp.spa"# <-- here
add myapp.spa in the INSTALLED_APPS in myapp/settings.py
INSTALLED_APPS=["django.contrib.admin","django.contrib.auth",..."django.contrib.staticfiles","myapp.spa",# <-- here
From the root folder of the project (ie the one with the frontend and myapp folders inside), run in two different terminal windows:
# 1st shell - sveltecd frontend
npm run dev
# 2nd shell - djangoDJANGO_DEBUG=ON poetry run python manage.py runserver
Go to localhost:8000 and you will see the Svelte app served directly from Django.
Try changing frontend/src/App.svelte or frontend/src/main.js, hit Ctrl+R and you will see it hot reloaded (from Django. Remember to disable cache or force the browser to refresh the page if you don’t see any change).
Don’t forget that you need the “npm run dev” in background in order to keep sync the bundle.* files under Django static folder.
Secure our SPA the Django way
Now we can leverage the standard Django features, such as authentication.
First, secure our View extending the LoginRequiredMixin. In myapp/spa/views.py
fromdjango.contribimportadminfromdjango.urlsimportpath,include# <-- here
frommyapp.spa.viewsimportSpaViewurlpatterns=[path("admin/",admin.site.urls),path("accounts/",include("django.contrib.auth.urls")),# <-- here
% if form.errors %
<p>Your username and password didn't match. Please try again.</p>
% endif %
% if next %
% if user.is_authenticated %
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
% else %
<p>Please login to see this page.</p>
% endif %
% endif %
<formmethod="post"action="% url 'login' %">
% csrf_token %
<table><tr><td> form.username.label_tag </td><td> form.username </td></tr><tr><td> form.password.label_tag </td><td> form.password </td></tr></table><inputtype="submit"value="login"><inputtype="hidden"name="next"value=" next "></form>
# Assumes you set up the password_reset view in your URLconf #
<p><ahref="% url 'password_reset' %">Lost password?</a></p></body></html>
INSTALLED_APPS=["django.contrib.admin","django.contrib.auth","django.contrib.contenttypes","django.contrib.sessions","django.contrib.messages","django.contrib.staticfiles",'rest_framework',# <-- here
Create a new Django app for the API
poetry run django-admin startapp api
we are using the default Django Session Authentication (via login form)
the API is available only for authenticated users
Now adjust frontend/src/App.svelte
<script>importonMountfrom"svelte";exportletname;letapimessage="Waiting for server...";onMount(async()=>letresp=awaitfetch("/api/greet").then((res)=>res.json());console.log(resp);apimessage=JSON.stringify(resp););</script><main><h1>Hello name!</h1><p>
Visit the <ahref="https://svelte.dev/tutorial">Svelte tutorial</a> to learn
how to build Svelte apps.
</p><h3>Data from server</h3>
Add the API to myapp/urls.py
fromdjango.contribimportadminfromdjango.urlsimportpath,includefrommyapp.spa.viewsimportSpaViewfrommyapp.api.viewsimportGreetingApi# <-- here
urlpatterns=[path("admin/",admin.site.urls),path("accounts/",include("django.contrib.auth.urls")),path("api/greet",GreetingApi.as_view()),# <-- here
Hit localhost:8000, login and enjoy your Django based SPA using Server Side Authentication and Rest API
Building for Production
In order to make our app ready to Production, we need:
poetry add gunicorn
collect all static files
poetry run python manage.py collectstatic
configure the allowed hosts
# change ALLOWED_HOSTS =  to
I’ve mentioned two categories of tools for web development. I still don’t know quite what to call these categories. Internal and external? Developer-facing and user-facing?
It’s a good way to think about things. There is nuance though, naturally. Sass is the first category since Sass never goes to users, it only makes CSS that goes to users. But it can still affect users because it could make CSS that is larger or smaller based on how you use it.
I know there are ways of getting React to behave more like a category one tool, but it is most definitely not the default behaviour. And default behaviour really, really matters. For React, the default behaviour is to assume all the code you write—and the tool you use to write it—will be sent over the wire to end users.
I think that’s fair to say, but it also seems like the story is slowly starting to change. I would think widespread usage is far off, but Server Components seem notable here because they are coming from the React team itself, just like SvelteKit is from the Svelte team itself.
[…] unlike Svelte, Astro allows you to use the same syntax as the incumbent, React. So if you’ve learned React—because that’s what you needed to learn to get a job—you don’t have to learn a new syntax in order to use Astro.
I know you probably can’t take an existing React site and convert it to Astro with the flip of a switch, but at least there’s a clear upgrade path.
This isn’t just theoretically true, it’s demonstrably true!
I guess that means Astro doesn’t change the categories—it’s a developer-facing tool. It just happens to take what would be a user-facing tool (even Svelte) and makes them almost entirely developer-facing.
And just because I’ve had a couple of other Astro links burning a hole in my pocket, Flavio has a good intro tutorial and here’s Drew McLellan and Matthew Phillips chatting Astro on a recent Smashing Podcast.
Josh Collingsworth is clearly a big fan of Svelte, so while this is a fun and useful comparison article, it’s here to crown Svelte the winner all the way through.
A few things I find compelling:
Svelte is reactive by default. This means when a variable is reassigned, every place it’s used or referenced also updates automatically. (React and Vue both require you to explicitly initialize reactive variables.)
P.S. I really like Josh’s header/footer random square motif so I tried to reverse engineer it:
Ever since I started using Svelte, my theme colours has always been flickering on page load. Finally I found a workaround
At first, I was changing my website’s theme colour inside the onMount function but the theme colours were always flickering. The reason was that, the body loads before the onMount function sets the theme colour.
Here’s a gif showing the problem
So as you can see, when the page is reloaded, the light theme is first shown, then after the document has finished loading, the onMount will then set the theme colour
The only probable solution is to set the theme colour before the body has even loaded. How do we do that❓, we insert a script tag inside the head element and this code will run before the body is loaded. That’s the best way we can prevent the flickering of colours.
Okay, so you can write this code in the component in which you use to toggle in-between the themes.
In order to access the head element, we used the <svelte:head> component. Then we created the script tag just as we would on our normal HTML pages. The next statements are the important ones, the reason why we used if (document) is that, this code first gets evaluated on the server before being rendered on the client, so if you try to access document on the server, it will pop up an error.
I know SvelteKit provides the browser constant through the $app/env module but mind you, this is not available in the custom script tag we made, you will need to use your own workaround and that’s why we are using document to check.
Then on the next line, we try to retrieve the theme from the localStorage, if it’s not set, it defaults to “light” theme;
Then the next steps are the addition of classes and setting of the theme in the localStorage.