Skip to content

A Deep Dive into SvelteKit Routing with Our Starter.dev GitHub Showcase Example

Introduction

SvelteKit is an excellent framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing. At the heart of SvelteKit is a filesystem-based router. The routes of your app — i.e. the URL paths that users can access — are defined by the directories in your codebase.

In this tutorial, we are going to discuss SvelteKit routing with an awesome SvelteKit GitHub showcase built by This Dot Labs. The showcase is built with the SvelteKit starter kit on starter.dev.

We are going to tackle:

  • Filesystem-based router
    • +page.svelte
    • +page.server
    • +layout.svelte
    • +layout.server
    • +error.svelte
  • Advanced Routing
    • Rest Parameters
    • (group) layouts
    • Matching

Below is the current routes folder.

Project Structure Sveltekit Showcase

Prerequisites

You will need a development environment running Node.js; this tutorial was tested on Node.js version 16.18.0, and npm version 8.19.2.

Filesystem-based router

The src/routes is the root route. You can change src/routes to a different directory by editing the project config.

// svelte.config.js

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    routes: "src/routes", // 👈 you can change it here to anything you want
  },
};

Each route directory contains one or more route files, which can be identified by their + prefix.

+page.svelte

A +page.svelte component defines a page of your app. By default, pages are rendered both on the server (SSR) for the initial request, and in the browser (CSR) for subsequent navigation.

In the below example, we see how to render a simple login page component:

// src/routes/signin/(auth)/+page.svelte

<script>
  import Auth from '$lib/components/auth/Auth.svelte';
</script>

<Auth />

+page.ts

Often, a page will need to load some data before it can be rendered. For this, we add a +page.js (or +page.ts, if you're TypeScript-inclined) module that exports a load function.

+page.server.ts

If your load function can only run on the server— ie, if it needs to fetch data from a database or you need to access private environment variables like API key— then you can rename +page.js to +page.server.js, and change the PageLoad type to PageServerLoad.

To pass top user repository data, and user’s gists to the client-rendered page, we do the following:

// src/routes/(authenticated)/(home)/+page.server.ts

import type { PageServerLoad } from "./$types";
import { mapUserReposToTopRepos, mapGistsToHomeGists } from "$lib/helpers";
import type {
  UserGistsApiResponse,
  UserReposApiResponse,
} from "$lib/interfaces";
import { ENV } from "$lib/constants/env";

export const load: PageServerLoad = async ({ fetch, parent }) => {
  const repoURL = new URL("/user/repos", ENV.GITHUB_URL);
  repoURL.searchParams.append("sort", "updated");
  repoURL.searchParams.append("per_page", "20");

  const { userInfo } = await parent();

  const gistsURL = new URL(
    `/users/${userInfo?.username}/gists`,
    ENV.GITHUB_URL
  );

  try {
    const reposPromise = await fetch(repoURL);

    const gistsPromise = await fetch(gistsURL);

    const [repoRes, gistsRes] = await Promise.all([reposPromise, gistsPromise]);

    const gists = (await gistsRes.json()) as UserGistsApiResponse;

    const repos = (await repoRes.json()) as UserReposApiResponse;

    return {
      topRepos: mapUserReposToTopRepos(repos),
      gists: mapGistsToHomeGists(gists),
      username: userInfo?.username,
    };
  } catch (err) {
    console.log(err);
  }
};

The page.svelte gets access to the data by using the data variable which is of type PageServerData.

    <!-- src/routes/(authenticated)/(home)/+page.svelte -->

    <script lang="ts">
    import TopRepositories from '$lib/components/TopRepositories/TopRepositories.svelte';
    import Gists from '$lib/components/Gists/Gists.svelte';
    import type { PageServerData } from './$types';
    export let data: PageServerData;
    </script>

    <div class="container">
      <div class="page-container">
        <aside>
        {#if data?.gists}
            <Gists gists={data.gists} />
        {/if}
        </aside>
        {#if data?.topRepos}
        <TopRepositories repos={data.topRepos} username={data?.username} />
        {/if}
      </div>
    </div>

    <style lang="scss">
    @use 'src/lib/styles/variables.scss';

    .page-container {
        display: grid;
        grid-template-columns: 1fr;
        background: variables.$gray100;
        @media (min-width: variables.$md) {
          grid-template-columns: 24rem 1fr;
        }
    }

    aside {
        background: variables.$white;
        padding: 2rem;
        @media (max-width: variables.$md) {
          order: 2;
        }
    }
    </style>

+layout.svelte

As there are elements that should be visible on every page, such as top-level navigation or a footer. Instead of repeating them in every +page.svelte, we can put them in layouts.

The only requirement is that the component includes a <slot> for the page content. For example, let's add a nav bar:

    <!-- src/routes/(authenticated)/+layout.svelte -->

    <script lang="ts">
    import NavBar from '$lib/components/NavBar/NavBar.svelte';
    import type { LayoutServerData } from './$types';

    export let data: LayoutServerData;
    </script>

    <div class="page">
    <header class="nav">
        <NavBar username={data?.userInfo.username} userAvatar={data?.userInfo.avatar} />
    </header>
    <main class="main">
        <slot />  // 👈  child routes of the layout page
    </main>
    </div>

+layout.server.ts

Just like +page.server.ts, your +layout.svelte component can get data from a load function in +layout.server.js, and change the type from PageServerLoad type to LayoutServerLoad.

// src/routes/(authenticated)/+layout.server.ts

import { ENV } from "$lib/constants/env";
import { remapContextUserAsync } from "$lib/helpers/context-user";
import type { LayoutServerLoad } from "./$types";
import { mapUserInfoResponseToUserInfo } from "$lib/helpers/user";

export const load: LayoutServerLoad = async ({ locals, fetch }) => {
  const getContextUserUrl = new URL("/user", ENV.GITHUB_URL);
  const response = await fetch(getContextUserUrl.toString());
  const contextUser = await remapContextUserAsync(response);
  locals.user = contextUser;
  return {
    userInfo: mapUserInfoResponseToUserInfo(locals.user),
  };
};

+error.svelte

If an error occurs during load, SvelteKit will render a default error page. You can customize this error page on a per-route basis by adding an +error.svelte file.

File Structure (authenticated) Github Showcase SvelteKit

In the showcase, an error.svelte page has been added for authenticated view in case of an error.

    <script>
        import { page } from '$app/stores';
        import ErrorMain from '$lib/components/ErrorPage/ErrorMain.svelte';
        import ErrorFlash from '$lib/components/ErrorPage/ErrorFlash.svelte';
    </script>

    <ErrorFlash message={$page.error?.message} />
    <ErrorMain status={$page.status} />

Advanced Routing

Rest Parameters

If the number of route segments is unknown, you can use spread operator syntax. This is done to implement Github’s file viewer.

/[org]/[repo]/tree/[branch]/[...file]

svelte-kit-scss.starter.dev/thisdot/starter.dev/blob/main/starters/svelte-kit-scss/README.md would result in the following parameters being available to the page:

{
  org: ‘thisdot’,
  repo: 'starter.dev',
  branch: 'main',
  file:/starters/svelte-kit-scss/README.md'
}

(group) layouts

By default, the layout hierarchy mirrors the route hierarchy. In some cases, that might not be what you want.

In the GitHub showcase, we would like an authenticated user to be able to have access to the navigation bar, error page, and user information. This is done by grouping all the relevant pages which an authenticated user can access.

File Structure (authenticated) Github Showcase SvelteKit

Grouping can also be used to tidy your file tree and ‘group’ similar pages together for easy navigation, and understanding of the project.

Blob File Structure Github Showcase SvelteKit

Matching

In the Github showcase, we needed to have a page to show issues and pull requests for a single repo. The route src/routes/(authenticated)/[username]/[repo]/[issues] would match /thisdot/starter.dev-github-showcases/issues or /thisdot/starter.dev-github-showcases/pull-requests but also /thisdot/starter.dev-github-showcases/anything and we don't want that. You can ensure that route parameters are well-formed by adding a matcher— which takes only issues or pull-requests, and returns true if it is valid– to your params directory.

// src/params/issue_search_type.ts

import { IssueSearchPageTypeFiltersMap } from "$lib/constants/matchers";
import type { ParamMatcher } from "@sveltejs/kit";

export const match: ParamMatcher = (param: string): boolean => {
  return Object.keys(IssueSearchPageTypeFiltersMap).includes(
    param?.toLowerCase()
  );
};
	// src/lib/constants/matchers.ts

    import { IssueSearchQueryType } from './issues-search-query-filters';

    export const IssueSearchPageTypeFiltersMap = {
        issues: ‘issues’,
        pulls: ’pull-requests’,
    };

    export type IssueSearchTypePage = keyof typeof IssueSearchPageTypeFiltersMap;

...and augmenting your routes:

[issueSearchType=issue_search_type]
Matching Routing Github Showcase SvelteKit

If the pathname doesn't match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404.

Note: Matchers run both on the server and in the browser.

Conclusion

In this article, we learned about basic and advanced routing in SvelteKit by using the SvelteKit showcase example. We looked at how to work with SvelteKit's Filesystem-based router, rest parameters, and (group) layouts.

If you want to learn more about SvelteKit, please check out the SvelteKit and SCSS starter kit and the SvelteKit and SCSS GitHub showcase. All the code for our showcase project is open source. If you want to collaborate with us or have suggestions, we're always welcome to new contributions.

Thanks for reading!

If you have any questions, or run into any trouble, feel free to reach out on Twitter.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

State of Svelte Wrap-up cover image

State of Svelte Wrap-up

In this State of Svelte event, our panelists discussed updates, LTS releases, and APIs, with Node.js maintainers, technical steering committee members, and collaborators. In this wrap-up, we will take a deeper look into these latest developments, and explore what is on the horizon for Svelte. You can watch the full State of Svelte event on the This Dot Media YouTube Channel. Here is a complete list of the host and panelists that participated in this online event. Hosts:__ - Tracy Lee, CEO, This Dot Labs, @ladyleet - Rob Ocel, Team Lead & Software Architect, @robocell Panelists:__ - Scott Spence, Svelte Society, Developer Relations Engineer at Storyblok, @spences10 - Brittney Postma, Founder Svelte Sirens & Software Engineer Design Systems at Provi, @BrittneyPostma - Geoff Rich, Senior Software Engineer, Ordergroove | Svelte Core Team, @geoffrich - Simon Holthausen, Software Engineer at Vercel | Full-time Svelte maintainer, @dummdidumm - Kevin Åberg Kultalahti, Co-founder & Technical Community Builder at Svelte Society, Main Organizer at Svelte Summit, @kevmodrome Svelte 4 The chat got off to a great start with a discussion about Svelte 4, and what we can expect with that release. Simon spoke about how it will be more of a maintenance update than anything else. This version of Svelte will raise the minimum required Node version and use newer versions of Typescript as well. There will also be other minor breaking changes, but the release will mainly be focused on internally updating the repository by converting it to a mono-repo. As soon as these updates are done, Simon said they will immediately begin work on Svelte 5. Typescript and Svelte Scott brings up the reasons for not using Typescript in Svelte. Simon said a decision was made to transition the Svelte repository from using Typescript to using Javascript. There were questions about why types and type safety were being taken away from the repository. Simon clarified that the repository will be getting rid of .TS files, but they are not getting rid of type checking with Typescript, and the code will still be fully typed checked at the same level as before. The plan is to do it through JS Docs. JS Docs provides the same level of type safety you get through Typescript, but there is no longer a need for a compile step when using JS Docs. There is also no need to ship any Source Maps, and it should be easier to debug. Kevin also wanted to be clear that Typescript can still be used when building a Svelte app. Why Svelte? Rob notes that the official release of Svelte happened about 4 months ago, and asks the panelists how the launch has been going so far. Kevin goes first, talking about how everyone with whom he has talked about it has been very excited about it. He talks about how the form actions and data loading are very popular. In other frameworks, you have to attach event listeners, and then do the fetching on clients. Svelte simplifies all of that, and allows you to get rid of a lot of code by using the features in Svelte. Svelte REPL Kevin talks about the Svelte REPL, and how it plays into why Svelte is getting so big. Svelte isn’t just easy, it’s the fact that it is social in the fact that you can share a REPL and show someone how to do something with Svelte. If you have an issue, you can usually find a solution in the Svelte REPL. Server and Client Geoff talks about this aspect of Svelte. He says that they were treated as two separate entities, and there was talk about how to make them more interconnected so that it’s easier to use the server data, and get it into components. In SvelteKit, you have a load function in a separate file that defines how that data is loaded. Svelte also calls a JSON endpoint and then that component in the JSON data. State Management Geoff brings up the simple state management model that Svelte has, and they really don’t want to give that up by implementing too many things like short syntax. Simon adds that there is no real reason to bloat the syntax in the Svelte files. He doesn’t want the interoperability that Svelte currently offers. Signals vs Store A question is brought up about Signals vs Store, and if they are the same. Simon talks more about how they are related, but they are not necessarily the same. He explains that the API for Store is a little more settled right now where the API for Signals is a little more in exploration. Usability is also different because Signals is more primitive, and everything is composed of functions which you call in a certain way. With Stores, you wrap a store and map the values that are pushed into something different. How the panelists found out about Svelte The latter part of this event focused on how each panelist found Svelte and got involved with it. It was a very interesting part of the conversation to hear the backgrounds of each panelist, and why they got more and more involved with Svelte and everything that was going with it. It went very in depth, and would be worth exploring more by watching the conversation unfold on the event video. Conclusion The panelists were very engaged, and there was a lot of dialogue about Svelte and the exciting things being done. The panelists also finished by bringing up ways to get involved with the Svelte community. You can watch the full State of Svelte event on the This Dot Media Youtube Channel....

Introducing the All New SvelteKit and SCSS Kit for starter.dev cover image

Introducing the All New SvelteKit and SCSS Kit for starter.dev

Introduction At This Dot Labs, we love Svelte. We've even created a starter.dev kit for SvelteKit that you can use to scaffold your next frontend project using SCSS, TypeScript, Vitest and Storybook. What is starter.dev? Starter.dev helps developers get started building web apps with a variety of frameworks, showcasing how various libraries can fit together to solve similar problems. To do that, This Dot Labs has built a series of showcase apps that recreate the experience of using GitHub. What is SvelteKit? How is it unique? SvelteKit is a full-stack framework that gives you the best of both worlds: the page is server-side rendered on your first visit, but when you navigate to other pages, they are client-side rendered. SvelteKit gives you levers for your pages to use SSR (server-side rendering), CSR (client-side rendering), SSG (static site generation), SPA (single page application) & MPA (multi page application). The core of SvelteKit provides a highly configurable rendering engine. Why SvelteKit and not Svelte? SvelteKit isn’t built on top of Svelte, but it’s a backend web framework where Svelte is used as the view layer. In theory, you could rip it out and replace it with another component framework that supports server-side rendering, and the same is true for other web frameworks. This allows us to deploy everything as a Node server, or even use Vercel and serverless functions. Other reasons to use SvelteKit include: 1. Pages (file based routing) 2. Endpoints (API routes) 3. Nested layouts (way more powerful than just nesting files because the segment of the URL maps to your component hierarchy) 4. Hot module replacement (instant updates in the browser when you make a change preserving application state) 5. Preprocessing (TypeScript, SCSS, and Pug among others) 6. Building component libraries (creating and publishing npm packages) 7. Deployment options (adapters for any platform) Building a SvelteKit showcase presented several challenges given its uniqueness and different approach to building web apps. This blog post details what we chose to include in our SvelteKit GitHub clone, and how we integrated them. Project Structures and Naming Conventions SvelteKit has unique conventions in its project structure and naming conventions. Project Files src This is the meat of the project: - lib` contains your library code, which can be imported via the `$lib` alias, or packaged up for distribution using `svelte-package`. It can be imported by using the `$lib/*` alias. - server` contains your server-only library code. SvelteKit will prevent you from importing these in client code. - components` contain single responsibility components that are imported in our routes. They typically contain three files: `.spec.ts` for unit tests, `.svelte` and `.stories.ts` for storybook stories. Barrel files were not necessary here. - images` contains images that can be imported, and used in the project. - stores` contains state that needs to be accessed by multiple unrelated components, or by a regular JS module. - styles` contains styles that can be imported in our Svelte applications. - params` contains any param matchers your app needs - routes` contains the `routes` of your application - app.html` is your page template — an HTML document containing the following placeholders: - %sveltekit.head%` — link and script elements needed by the app, plus any head content - %sveltekit.body%` — the markup for a rendered page. This should live inside a `` or other element, rather than directly inside the body element, to prevent bugs caused by browser extensions injecting elements that are then destroyed by the hydration process. SvelteKit will warn you in development if this is not the case - %sveltekit.assets%` — either paths.assets, if specified, or a relative path to paths.base - %sveltekit.nonce%` — a CSP nonce for manually included links and scripts, if used - hooks.server.ts` contains your application's hooks static Any static assets that should be served as-is, like robots.txt` or `favicon.png`, go in here. svelte.config.js This file contains your Svelte and SvelteKit configuration. tsconfig.json Since SvelteKit relies on certain configuration being set a specific way, it generates its own .svelte-kit/tsconfig.json` file, which your own config extends. vite.config.js A SvelteKit project is really just a Vite project that uses the @sveltejs/kit/vite plugin, along with any other Vite configuration. Routing To get a deep dive of how routing works with SvelteKit, please check out this article. SCSS SvelteKit supports a number of CSS preprocessors. For people who are new to Svelte or SvelteKit, the syntax for using SCSS or SASS is simple, just need to add the lang="sass" attribute to the style tag. `scss ul { list-style-type: circle; li { position: relative; } } ` `sh npm i -D sass ` Then add SCSS support with the svelte-preprocess` package. `js // svelte.config.js import adapter from "@sveltejs/adapter-auto"; import preprocess from "svelte-preprocess"; / @type {import('@sveltejs/kit').Config} */ const config = { // Consult https://github.com/sveltejs/svelte-preprocess // for more information about preprocessors preprocess: preprocess({ typescript: true, scss: true }), kit: { adapter: adapter(), }, }; export default config; ` Why SCSS and not Tailwind? Tailwind CSS has been used by several other starter kits. We otherwise decided to go with SCSS as the syntax is simple and easily understood by even beginners. Vitest Test Driven Development (TDD) is one of the best ways to ensure your code works like it's supposed to work. It can also help you create reliable builds during continuous deployments. Vitest is an up-and-coming testing framework which has similar functionality to Jest. Since we are using Vite as our build tool for Svelte in this kit, Vitest has very good integration with Vite, and offers a similar testing environment without needing extra configuration. To test Svelte components that seemed to be hard to test. Such as two-way bindings, name slots, Context API, etc., we need to add more configuration. We added @testing-library/svelte`, `jsdom` and `@testing-library/jest-dom` that allow for similar functionality as Jest. `sh npm i -D @testing-library/svelte @testing-library/jest-dom jsdom ` We ensured the $lib` alias is supported in our tests by resolving the alias in our `vite.config.ts`. We also added a `setupTest.ts` to add `@testing-library/jest-dom` matchers & mocks of SvelteKit modules. `ts // vite.config.ts import { sveltekit } from "@sveltejs/kit/vite"; import type { UserConfig } from "vite"; import { configDefaults, type UserConfig as VitestConfig } from "vitest/config"; import path from "path"; const config: UserConfig & { test: VitestConfig["test"] } = { plugins: [sveltekit()], define: { // Eliminate in-source test code "import.meta.vitest": "undefined", }, test: { // jest like globals globals: true, environment: "jsdom", // in-source testing includeSource: ["src//*.{js,ts,svelte}"], // Add @testing-library/jest-dom matchers & mocks of SvelteKit modules setupFiles: ["./setupTests.ts"], // Exclude files in c8 coverage: { exclude: ["setupTest.ts"], }, deps: { // Put Svelte component here, e.g., inline: [/svelte-multiselect/, /msw/] inline: [], }, // Exclude playwright tests folder exclude: [...configDefaults.exclude, "tests"], }, // Ensure the $lib alias is supported in our tests resolve: { alias: { $lib: path.resolve(dirname, "./src/lib"), }, }, }; export default config; ` `ts // setupTest.ts / eslint-disable @typescript-eslint/no-empty-function */ import matchers from "@testing-library/jest-dom/matchers"; import { expect, vi } from "vitest"; import type { Navigation, Page } from "@sveltejs/kit"; import { readable } from "svelte/store"; import as environment from "$app/environment"; import as navigation from "$app/navigation"; import as stores from "$app/stores"; // Add custom jest matchers expect.extend(matchers); // Mock SvelteKit runtime module $app/environment vi.mock("$app/environment", (): typeof environment => ({ browser: false, dev: true, building: false, version: "any", })); // Mock SvelteKit runtime module $app/navigation vi.mock("$app/navigation", (): typeof navigation => ({ afterNavigate: () => {}, beforeNavigate: () => {}, disableScrollHandling: () => {}, goto: () => Promise.resolve(), invalidate: () => Promise.resolve(), invalidateAll: () => Promise.resolve(), preloadData: () => Promise.resolve(), preloadCode: () => Promise.resolve(), })); // Mock SvelteKit runtime module $app/stores vi.mock("$app/stores", (): typeof stores => { const getStores: typeof stores.getStores = () => { const navigating = readable(null); const page = readable({ url: new URL("http://localhost"), params: {}, route: { id: null, }, status: 200, error: null, data: {}, form: undefined, }); const updated = { subscribe: readable(false).subscribe, check: () => false, }; return { navigating, page, updated }; }; const page: typeof stores.page = { subscribe(fn) { return getStores().page.subscribe(fn); }, }; const navigating: typeof stores.navigating = { subscribe(fn) { return getStores().navigating.subscribe(fn); }, }; const updated: typeof stores.updated = { subscribe(fn) { return getStores().updated.subscribe(fn); }, check: () => false, }; return { getStores, navigating, page, updated, }; }); ` If you want to see some test recipes you can use on your SvelteKit projects, check out our SvelteKit-SCSS Github showcase. Storybook Like many of the other starter.dev kits, the SvelteKit starter uses Storybook to interactively view and build components in isolation. For more information on Storybook and SvelteKit visit the article. Linting ESLint` and `Prettier` are useful tools for keeping the project neat and consistent among multiple contributors. To quickly format your project, run: `sh npm run format ` Running ESLint and Prettier as part of your git workflow is important because it helps you fail fast. This helps us, as contributors, to have a more consistent production codebase. We achieved this in our SvelteKit-SCSS Github showcase with the help of Husky`, `lint-staged` and `Prettier`. How does lint-staged` work? It's specifically designed to work on "staged" files, which are files you've changed or created, but haven't yet committed to your project. Working on staged files limits the number of files you need to lint at any given time, and makes the workflow faster. We configured lint-staged` in our `package.json`. This runs `Prettier` pre-commit, and ensures the code is up to our ESLint standards. `json // package.json "lint-staged": { "src//*{.js,.ts,.html,.css,.svelte}": [ "prettier --plugin-search-dir . --write ." ], "src//*{.js,.ts,.svelte}": "eslint ." }, ` The commands you configure will run "pre-commit". As you're attempting to commit files to your project you'll see ESLint run in your terminal. Once it's done you may have successfully committed, or find yourself with linting errors you need to fix before you're able to commit the code. This works hand-in-hand with husky`. Husky uses distinct bash files with filenames that match the workflow step they correspond to, e.g. "pre-commit". `sh #!/bin/sh . "$(dirname "$0")//husky.sh" cd svelte-kit-scss npx lint-staged ` Running ESLint, or Prettier as part of your git workflow is important because it helps you fail fast, which helps contributors have a more consistent production codebase. Conclusion SvelteKit is a relatively new and novel framework. The structure represents our best judgment of how a basic SvelteKit application should be. We welcome everyone to take a look, and contribute back to the SvelteKit-SCSS and our SvelteKit-SCSS Github showcase if you have any improvements that you would like to propose!...

Harnessing the Power of Threlte - Building Reactive Three.js Scenes in Svelte cover image

Harnessing the Power of Threlte - Building Reactive Three.js Scenes in Svelte

Introduction Web development has evolved to include immersive 3D experiences through libraries like Three.js. This powerful JavaScript library enables the creation of captivating 3D scenes within browsers. Three.js: The 3D Powerhouse Three.js democratizes 3D rendering, allowing developers of all skill levels to craft interactive 3D worlds. Svelte Ecosystem: Svelte Cubed and Svelthree The Svelte ecosystem presents solutions like Svelte Cubed and Svelthree, which bridges Svelte with Three.js, offering streamlined reactivity for 3D web experiences. Introducing Threlte v6: Uniting SvelteKit 1.0, Svelte 4, and TypeScript Threlte v6 is a rendering and component library for Svelte that seamlessly integrates Three.js. By harnessing TypeScript's types, it provides a robust and delightful coding experience. In this tutorial, we'll showcase Threlte's capabilities by building an engaging website header: an auto-rotating sphere that changes color on mouse down. Using Threlte v6, SvelteKit 1.0, and Three.js, we're set to create a visually stunning experience. Let's dive in! Setting up Threlte Before building our scene, we need to set up Threlte. We can scaffold a new project using the CLI or manually install Threlte in an existing project. Option 1: Scaffold a New Threlte Project Create a new SvelteKit project and install Threlte with: `bash npm create threlte my-project ` Option 2: Manual Installation For an existing project, select the necessary Threlte packages and install: `bash npm install three @threlte/core @threlte/extras @threlte/rapier @dimforge/rapier3d-compat @threlte/theatre @theatre/core @theatre/studio @types/three ` Configuration adjustments for SvelteKit can be made in the "vite.config.js" file: `js const config = { // ... ssr: { noExternal: ['three'] } }; ` With Threlte configured, we're ready to build our interactive sphere. In the next chapter, we'll lay the groundwork for our exciting 3D web experience! Exploring the Boilerplate of Threlte Upon scaffolding a new project using npm create threlte`, a few essential boilerplate files are generated. In this chapter, we'll examine the code snippets from three of these files: `lib/components/scene.svelte`, `routes/+page.svelte`, and `lib/components/app.svelte`. 1. lib/components/scene.svelte`: This file lays the foundation for our 3D scene. Here's a brief breakdown of its main elements: - Perspective Camera**: Sets up the camera view with a specific field of view and position, and integrates `OrbitControls` for auto-rotation and zoom management. - Directional and Ambient Lights**: Defines the lighting conditions to illuminate the scene. - Grid**: A grid structure to represent the ground. - ContactShadows**: Adds shadow effects to enhance realism. - Float**: Wraps around 3D mesh objects and defines floating properties, including intensity and range. Various geometrical shapes like BoxGeometry, TorusKnotGeometry, and IcosahedronGeometry are included here. 2. routes/+page.svelte`: This file handles the ui of the index page and imports all necessary components we need to bring our vibrant design to life. 3. lib/components/app.svelte`: This file is where you would typically define the main application layout, including styling and embedding other components. Heading to the Fun Stuff With the boilerplate components explained, we're now ready to dive into the exciting part of building our interactive 3D web experience. In the next section, we'll begin crafting our auto-rotating sphere, and explore how Threlte's robust features will help us bring it to life. Creating a Rotating Sphere Scene In this chapter, we'll walk you through creating an interactive 3D sphere scene using Threlte. We'll cover setting up the scene, the sphere, the camera and lights, and finally the interactivity that includes a scaling effect and color changes. 1. Setting Up the Scene First, we need to import the required components and utilities from Threlte. `typescript import { T } from '@threlte/core'; import { OrbitControls } from '@threlte/extras'; ` 2. Setting Up the Sphere We'll create the 3D sphere using Threlte's ` and `` components. `svelte ` 1. `: This is a component from Threlte that represents a 3D object, which in this case is a sphere. It's the container that holds the geometry and material of the sphere. 2. `: This is the geometry of the sphere. It defines the shape and characteristics of the sphere. The `args` attribute specifies the parameters for the sphere's creation: - The first argument (1`) is the radius of the sphere. - The second argument (32`) represents the number of width segments. - The third argument (32`) represents the number of height segments. 3. `: This is the material applied to the sphere. It determines how the surface of the sphere interacts with light. The `color` attribute specifies the color of the material. In this case, the color is dynamic and defined by the `sphereColor` variable, which updates based on user interaction. The `roughness` attribute controls the surface roughness of the sphere, affecting how it reflects light. 3. Setting Up the Camera and Lights Next, we'll position the camera and add lights to create a visually appealing scene. `svelte ` 1. `: This component represents the camera in the scene. It provides the viewpoint through which the user sees the 3D objects. The `position` attribute defines the camera's position in 3D space. In this case, the camera is positioned at `(-10, 20, 10)`. The `fov` attribute specifies the field of view, which affects how wide the camera's view is. - makeDefault`: This attribute makes this camera the default camera for rendering the scene. 2. `: This component provides controls for easy navigation and interaction with the scene. It allows the user to pan, zoom, and orbit around the objects in the scene. The attributes within the `` component configure its behavior: - enableZoom`: Disables zooming using the mouse scroll wheel. - enablePan`: Disables panning the scene. - enableDamping`: Enables a damping effect that smoothens the camera's movement. - autoRotate`: Enables automatic rotation of the camera around the scene. - autoRotateSpeed`: Defines the speed of the auto-rotation. 3. `: This component represents a directional light source in the scene. It simulates light coming from a specific direction. The attributes within the `` component configure the light's behavior: - intensity`: Specifies the intensity of the light. - position.x` and `position.y`: Define the position of the light source in the scene. 4. `: This component represents an ambient light source in the scene. It provides even lighting across all objects in the scene. The `intensity` attribute controls the strength of the ambient light. 4. Interactivity: Scaling and Color Changes Now we'll add interactivity to the sphere, allowing it to scale and change color in response to user input. First, we'll import the required utilities for animation and set up a spring object to manage the scale. `typescript import { spring } from 'svelte/motion'; import { onMount } from 'svelte'; import { interactivity } from '@threlte/extras'; interactivity(); const scale = spring(0, { stiffness: 0.1 }); onMount(() => { scale.set(1); }); ` We'll update the sphere definition to include scaling: `svelte scale.set(1.1)} on:pointerleave={() => scale.set(1)}> ` Lastly, we'll add code to update the color of the sphere based on the mouse's position within the window. `typescript let mousedown = false; let rgb: number[] = []; function updateSphereColor(e: MouseEvent) { if (mousedown) { rgb = [ Math.floor((e.pageX / window.innerWidth) 255), Math.floor((e.pageY / window.innerHeight) 255), 150 ]; } } window.addEventListener('mousedown', () => (mousedown = true)); window.addEventListener('mouseup', () => (mousedown = false)); window.addEventListener('mousemove', updateSphereColor); $: sphereColor = rgb.join(','); ` We have successfully created a rotating sphere scene with scaling and color-changing interactivity. By leveraging Threlte's capabilities, we have built a visually engaging 3D experience that responds to user input, providing a dynamic and immersive interface. Adding Navigation and Scroll Prompt in `app.svelte` In this chapter, we'll add a navigation bar and a scroll prompt to our scene. The navigation bar provides links for user navigation, while the scroll prompt encourages the user to interact with the content. Here's a step-by-step breakdown of the code: 1. Importing the Canvas and Scene The Canvas` component from Threlte serves as the container for our 3D scene. We import our custom `Scene` component to render within the canvas. `typescript import { Canvas } from '@threlte/core'; import Scene from './Scene.svelte'; ` 2. Embedding the 3D Scene The Canvas` component wraps the `Scene` component to render the 3D content. It is positioned absolutely to cover the full viewport, and the `z-index` property ensures that it's layered behind the navigation elements. `svelte ` 3. Adding the Navigation Bar We use a ` element to create a horizontal navigation bar at the top of the page. It contains a home link and two navigation list items. The styling properties ensure that the navigation bar is visually appealing and positioned correctly. `svelte Home Explore Learn ` 4. Adding the Scroll Prompt We include a "Give a scroll" prompt with an ` element to encourage user interaction. It's positioned near the bottom of the viewport and styled for readability against the background. `svelte Give a scroll ` 5. Styling the Components Finally, the provided CSS styles control the positioning and appearance of the canvas, navigation bar, and scroll prompt. The CSS classes apply appropriate color, font, and layout properties to create a cohesive and attractive design. `css .sphere-canvas { width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 1; } nav { display: flex; color: white; z-index: 2; position: relative; padding: 4rem 8rem; justify-content: space-between; align-items: center; } nav a { text-decoration: none; color: white; font-weight: bold; } nav ul { display: flex; list-style: none; gap: 4rem; } h1 { color: white; z-index: 2; position: absolute; font-size: 3rem; left: 50%; top: 75%; transform: translate(-50%, -75%); } ` Head to the github repo to view the full code.** Check out the result: https://threlte6-spinning-ball.vercel.app/** Conclusion We've successfully added navigation and a scroll prompt to our Threlte project in the app.svelte` file. By layering 2D HTML content with a 3D scene, we've created an interactive user interface that combines traditional web design elements with immersive 3D visuals....

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights) cover image

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights)

We take accessibility quite seriously here at This Dot because we know it's important. Still, throughout my career, I've seen many projects where accessibility was brushed aside for reasons like "our users don't really use keyboard shortcuts" or "we need to ship fast; we can add accessibility later." The truth is, that "later" often means "never." And it turns out, anyone could break their hand, like I did. I broke my dominant hand and spent four weeks in a cast, effectively rendering it useless and forcing me to work left-handed. I must thus apologize for the misleading title; this post should more accurately be dubbed "second-hand" accessibility insights. The Perspective of a Developer Firstly, it's not the end of the world. I adapted quickly to my temporary disability, which was, for the most part, a minor inconvenience. I had to type with one hand, obviously slower than my usual pace, but isn't a significant part of a software engineer's work focused on thinking? Here's what I did and learned: - I moved my mouse to the left and started using it with my left hand. I adapted quickly, but the experience wasn't as smooth as using my right hand. I could perform most tasks, but I needed to be more careful and precise. - Many actions require holding a key while pressing a mouse button (e.g., visiting links from the IDE), which is hard to do with one hand. - This led me to explore trackpad options. Apart from the Apple Magic Trackpad, choices were limited. As a Windows user (I know, sorry), that wasn't an option for me. I settled for a cheap trackpad from Amazon. A lot of tasks became easier; however, the trackpad eventually malfunctioned, sending me back to the mouse. - I don't know a lot of IDE shortcuts. I realized how much I've been relying on a mouse for my work, subconsciously refusing to learn new keyboard shortcuts (I'll be returning my senior engineer license shortly). So I learned a few new ones, which is good, I guess. - Some keyboard shortcuts are hard to press with one hand. If you find yourself in a similar situation, you may need to remap some of them. - Copilot became my best friend, saving me from a lot of slow typing, although I did have to correct and rewrite many of its suggestions. The Perspective of a User As a developer, I was able to get by and figure things out to be able to work effectively. As a user, however, I got to experience the other side of the coin and really feel the accessibility (or lack thereof) on the web. Here are a few insights I gained: - A lot of websites apparently tried_ to implement keyboard navigation, but failed miserably. For example, a big e-commerce website I tried to use to shop for the aforementioned trackpad seemed to work fine with keyboard navigation at first, but once I focused on the search field, I found myself unable to tab out from it. When you make the effort to implement keyboard navigation, please make sure it works properly and it doesn't get broken with new changes. I wholeheartedly recommend having e2e tests (e.g. with Playwright) that verify the keyboard navigation works as expected. - A few websites and web apps I tried to use were completely unusable with the keyboard and were designed to be used with a mouse only. - Some sites had elaborate keyboard navigation, with custom keyboard shortcuts for different functionality. That took some time to figure out, and I reckon it's not as intuitive as the designers thought it would be. Once a user learns the shortcuts, however, it could make their life easier, I suppose. - A lot of interactive elements are much smaller than they should be, making it hard to accurately click on them with your weaker hand. Designers, I beg you, please make your buttons bigger. I once worked on an application that had a "gloves mode" for environments where the operators would be using gloves, and I feel like maybe the size we went with for the "gloves mode" should be the standard everywhere, especially as screens get bigger and bigger. - Misclicking is easy, especially using your weaker hand. Be it a mouse click or just hitting an Enter key on accident. Kudos to all the developers who thought about this and implemented a confirmation dialog or other safety measures to prevent users from accidentally deleting or posting something. I've however encountered a few apps that didn't have any of these, and those made me a bit anxious, to be honest. If this is something you haven't thought about when developing an app, please start doing so, you might save someone a lot of trouble. Some Second-Hand Insights I was only a little bit impaired by being temporarily one-handed and it was honestly a big pain. In this post, I've focused on my anecdotal experience as a developer and a user, covering mostly keyboard navigation and mouse usage. I can only imagine how frustrating it must be for visually impaired users, or users with other disabilities, to use the web. I must confess I haven't always been treating accessibility as a priority, but I've certainly learned my lesson. I will try to make sure all the apps I work on are accessible and inclusive, and I will try to test not only the keyboard navigation, ARIA attributes, and other accessibility features, but also the overall experience of using the app with a screen reader. I hope this post will at least plant a little seed in your head that makes you think about what it feels like to be disabled and what would the experience of a disabled person be like using the app you're working on. Conclusion: The Humbling Realities of Accessibility The past few weeks have been an eye-opening journey for me into the world of accessibility, exposing its importance not just in theory but in palpable, daily experiences. My short-term impairment allowed me to peek into a life where simple tasks aren't so simple, and convenient shortcuts are a maze of complications. It has been a humbling experience, but also an illuminating one. As developers and designers, we often get caught in the rush to innovate and to ship, leaving behind essential elements that make technology inclusive and humane. While my temporary disability was an inconvenience, it's permanent for many others. A broken hand made me realize how broken our approach towards accessibility often is. The key takeaway here isn't just a list of accessibility tips; it's an earnest appeal to empathize with your end-users. "Designing for all" is not a checkbox to tick off before a product launch; it's an ongoing commitment to the understanding that everyone interacts with technology differently. When being empathetic and sincerely thinking about accessibility, you never know whose life you could be making easier. After all, disability isn't a special condition; it's a part of the human condition. And if you still think "Our users don't really use keyboard shortcuts" or "We can add accessibility later," remember that you're not just failing a compliance checklist, you're failing real people....