Skip to content

Integrating Storybook with SvelteKit, TypeScript, and SCSS

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Introduction

Storybook is a great tool for testing, and visualizing your components in different states.

Storybook allows teams to collaborate to develop durable UIs in isolation. It allows users to implement reusable components without fussing with data, APIs, or business logic.

In this article, we will discuss how to integrate Storybook in a SvelteKit project with TypeScript, and SCSS support.

Zero-config set up

To get started, run the following in the root of an existing Svelte project:

    npx sb@next init

This detects the project type, installs @storybook/svelte, and adds some sample files to demonstrate the basics of Storybook. Running npm run storybook gives you the following zero-config set up on http://localhost:6006

Example Browse All Stories

Project Structure

Our project structure is already set up by SvelteKit, and Storybook initialization has created a .storybook folder. Still, we need to make some changes to the Storybook file extension, since our project is in TypeScript. This is a snippet of the folders in our project:

    ├── .storybook
        ├── main.cjs
        ├── preview.js
        └── preview-head.html
    ├── vite.config.ts
    └── src

Add SCSS support

To add SCSS support, we need the @storybook/preset-scss addon.

Install sass, @storybook/preset-scss, and other relevant style loaders.

npm i -D sass @storybook/preset-scss css-loader sass-loader style-loader

Navigate to storybook/main.cjs

Add @storybook/preset-scss to the addons' array.

	addons: [
		'@storybook/addon-links',
		'@storybook/addon-essentials',
		'@storybook/addon-interactions',
		'@storybook/preset-scss' // add here
	],

Add $lib alias support

Navigate to .storybook/main.cjs.

Import mergeConfig from Vite. This deeply merges two Vite configs.

Import path. The path module provides utilities for working with file, and directory paths.

Finally, we need to resolve the $lib to point to ../src/lib for Storybook.

async viteFinal(config) {
    return mergeConfig(config, {
        resolve: {
            alias: { $lib: path.resolve(__dirname, '../src/lib') }
        }
    });
}

Your .storybook/main.cjs should contain the following:

// .storybook/main.cjs

const { mergeConfig } = require("vite");
const path = require("path");

module.exports = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
    "@storybook/preset-scss",
  ],
  framework: {
    name: "@storybook/sveltekit",
    options: {},
  },
  docs: {
    autodocs: "tag",
  },
  async viteFinal(config) {
    return mergeConfig(config, {
      resolve: {
        alias: { $lib: path.resolve(__dirname, "../src/lib") },
      },
    });
  },
};

Creating Stories

We will start by creating a Greeting.svelte file that receives a message from our server.

    <script lang="ts">
        export let message: string;
    </script>

    <div class="fetch-container">
        <header>
            <h1>SvelteKit Fetch Data from API</h1>
        </header>
        <div>
            Message: {message}
        </div>
    </div>

    <style lang="scss">
        .fetch-container {
            text-align: center;

            header {
              margin: 1.25rem auto;
              width: 40%;

              h1 {
                padding: 0.9375rem 0;
                font-size: 2rem;
                text-align: center;
                border-bottom: 5px solid #1d4ed8;
              }
            }

            div {
              font-size: 1.2rem;
              display: flex;
              justify-content: center;
            }
        }
    </style>

Then a Greeting.stories.ts with a message argument:

    import Greeting from './Greeting.svelte';

    export default {
        component: Greeting,
        title: 'Example/Greeting',
        excludeStories: /.*Data$/,
        argTypes: {
            message: 'from Storybook',
        },
    };

    const Template = ({ ...args }) => ({
        Component: Greeting,
        props: args,
    });

    export const Default = Template.bind({});
        Default.args = {
        message: 'from This Dot’,
    };

Simply run npm run storybook to see if your story is running:

Storybook-example

You can edit the argument to test different messages.

Conclusion

In this article, we learned how to set up Storybook in a SvelteKit project, and created our first story.

If you are looking to bootstrap your next project, check out our starter kit that uses SvelteKit and SCSS.

Thanks for reading!

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

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Svelte 5 is Here! cover image

Svelte 5 is Here!

Svelte 5 was finally released after a long time in development. Fortunately, we've been able to test it for some time, and now it has a stable release. Let's dig into its features and why this is such a significant change, even though Svelte 4 code is almost 100% compatible. Svelte syntax everywhere This is one of my favorite additions. Previously, Svelte syntax was limited to a component element. Refactoring code from a component and moving it to a JavaScript file worked differently. Now you can use the .svelte.js or .svelte.ts extension, which allows you to use the same syntax everywhere. It's important to note that it's a way to express that this is not just JS, and what you write may be compiled to something else, just like .svelte files are not just html even though they look very similar. Runes The introduction of runes is one of the most significant changes in Svelte. Many users felt attracted to variables being instantly reactive in previous versions. ` There was, however, a lot of magic underneath and a considerable amount of work from the compiler to make it behave reactively. $state In Svelte 5, a reactive variable has to be explicitly declared using the $state rune, which brings a clear distinction between what is reactive and what isn’t. ` In the previous code, $state is not an actual function being called; it's a hint for the compiler to do something special with this declaration. Rune names always start with a dollar sign ($) and do not need to be imported. However, there’s much more to the changes than just the way we declare reactive variables. Runes bring a lot of new features that make Svelte 5 a great improvement. In Svelte 5, objects or arrays use Proxies to allow for granular reactivity, meaning that individual properties in an object are reactive, even if they are nested objects or arrays. If you modify a property, it will only update that property and will not trigger an update on the whole object. It also supports triggering updates when methods like array.push are called. In previous versions, an assignment was required to trigger an update: ` To have a similar behavior of svelte 4 (no deep reactivity), use the $state.raw() rune. This syntax of .* is common for related features of a rune: $state.raw() will require an assignment to trigger reactivity. ` Because proxies are used, you may need to extract the underlying state instead of the proxy itself. In that case, $state.snapshot() should be used: ` $derived We will use $derived and $derived.by to declare a derived state. The two runes are essentially the same, except one allows us to use a function instead of an expression, allowing for more complex operations to calculate the derived values. ` $effect Effects are useful for running something triggered by a change in state. One of the things you'll note from the $effect rune is that dependencies don't need to be explicit. Those will be picked from reactive values read synchronously in the body of the effect function. ` Something to bear in mind is that these dependencies are picked each time the effect runs. The values read during the last run become the dependencies of the effect. ` Depending on the result of the random method, foo or bar will stop being a dependency of the effect. You should place them outside the condition so they can trigger reruns of the effect. *Variants* of the effect rune are $effect.pre, which runs before a DOM update, and $effect.tracking(), which checks for the context of the effect (true for an effect inside an effect or in the template). $props This rune replaces the export let keywords used in previous versions to define a component's props. To use the $props syntax, we can consider it to return an object with all the properties a component receives. We can use JavaScript syntax to destructure it or use the rest property if we want to retrieve every property not explicitly destructured.. ` $bindable If you want to mutate a prop so that the change flows back to the parent, you can use the $bindable prop to make it work in both directions. A parent component can pass a value to a child component, and the child component is able to modify it, returning the data back to the parent component. ` $inspect The $inspect rune only works in dev mode and will track dependencies deeply. By default, it will call console.log with the provided argument whenever it detects a change. To change the underlying function, use $inspect.with().with): ` $host The host rune gives access to the host element when compiling as a custom element: ` ` Other changes Another important change is that component events are just props in Svelte 5, so you can destructure them using the $props() rune: ` A special children property can be used to project content into a component instead of using slots. Inside the components, use the [@render[(https://svelte.dev/docs/svelte/@render) tag to place them. ` Optional chaining (?.) prevents from attempting to render it if no children are passed in. If you need to render different components in different places (named slots before Svelte 5, you can pass them as any other prop, and use @render with them. Snippets Snippets allow us to declare markup slices and render them conveniently using the render tag (@render). They can take any number of arguments. ` Snippets can also be passed as props to other components. ` ` Conclusion Some exciting changes to the Svelte syntax were introduced while, at the same time, maximum compatibility efforts were made. This was a massive rewrite with numerous improvements in terms of performance, bundle size, and DX. Besides that, a new CLI has been released, making the whole experience of starting a project or adding features delightful. If you haven't tried Svelte before, it's a great time to try it now....

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. ` ` Then add SCSS support with the svelte-preprocess package. ` 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. ` 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. ` ` 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: ` 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. ` 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". ` 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!...

Understanding Sourcemaps: From Development to Production cover image

Understanding Sourcemaps: From Development to Production

What Are Sourcemaps? Modern web development involves transforming your source code before deploying it. We minify JavaScript to reduce file sizes, bundle multiple files together, transpile TypeScript to JavaScript, and convert modern syntax into browser-compatible code. These optimizations are essential for performance, but they create a significant problem: the code running in production does not look like the original code you wrote. Here's a simple example. Your original code might look like this: ` After minification, it becomes something like this: ` Now imagine trying to debug an error in that minified code. Which line threw the exception? What was the value of variable d? This is where sourcemaps come in. A sourcemap is a JSON file that contains a mapping between your transformed code and your original source files. When you open browser DevTools, the browser reads these mappings and reconstructs your original code, allowing you to debug with variable names, comments, and proper formatting intact. How Sourcemaps Work When you build your application with tools like Webpack, Vite, or Rollup, they can generate sourcemap files alongside your production bundles. A minified file references its sourcemap using a special comment at the end: ` The sourcemap file itself contains a JSON structure with several key fields: ` The mappings field uses an encoding format called VLQ (Variable Length Quantity) to map each position in the minified code back to its original location. The browser's DevTools use this information to show you the original code while you're debugging. Types of Sourcemaps Build tools support several variations of sourcemaps, each with different trade-offs: Inline sourcemaps: The entire mapping is embedded directly in your JavaScript file as a base64 encoded data URL. This increases file size significantly but simplifies deployment during development. ` External sourcemaps: A separate .map file that's referenced by the JavaScript bundle. This is the most common approach, as it keeps your production bundles lean since sourcemaps are only downloaded when DevTools is open. Hidden sourcemaps: External sourcemap files without any reference in the JavaScript bundle. These are useful when you want sourcemaps available for error tracking services like Sentry, but don't want to expose them to end users. Why Sourcemaps During development, sourcemaps are absolutely critical. They will help avoid having to guess where errors occur, making debugging much easier. Most modern build tools enable sourcemaps by default in development mode. Sourcemaps in Production Should you ship sourcemaps to production? It depends. While security by making your code more difficult to read is not real security, there's a legitimate argument that exposing your source code makes it easier for attackers to understand your application's internals. Sourcemaps can reveal internal API endpoints and routing logic, business logic, and algorithmic implementations, code comments that might contain developer notes or TODO items. Anyone with basic developer tools can reconstruct your entire codebase when sourcemaps are publicly accessible. While the Apple leak contained no credentials or secrets, it did expose their component architecture and implementation patterns. Additionally, code comments can inadvertently contain internal URLs, developer names, or company-specific information that could potentially be exploited by attackers. But that’s not all of it. On the other hand, services like Sentry can provide much more actionable error reports when they have access to sourcemaps. So you can understand exactly where errors happened. If a customer reports an issue, being able to see the actual error with proper context makes diagnosis significantly faster. If your security depends on keeping your frontend code secret, you have bigger problems. Any determined attacker can reverse engineer minified JavaScript. It just takes more time. Sourcemaps are only downloaded when DevTools is open, so shipping them to production doesn't affect load times or performance for end users. How to manage sourcemaps in production You don't have to choose between no sourcemaps and publicly accessible ones. For example, you can restrict access to sourcemaps with server configuration. You can make .map accessible from specific IP addresses. Additionally, tools like Sentry allow you to upload sourcemaps during your build process without making them publicly accessible. Then configure your build to generate sourcemaps without the reference comment, or use hidden sourcemaps. Sentry gets the mapping information it needs, but end users can't access the files. Learning from Apple's Incident Apple's sourcemap incident is a valuable reminder that even the largest tech companies can make deployment oversights. But it also highlights something important: the presence of sourcemaps wasn't actually a security vulnerability. This can be achieved by following good security practices. Never include sensitive data in client code. Developers got an interesting look at how Apple structures its Svelte codebase. The lesson is that you must be intentional about your deployment configuration. If you're going to include sourcemaps in production, make that decision deliberately after considering the trade-offs. And if you decide against using public sourcemaps, verify that your build process actually removes them. In this case, the public repo was quickly removed after Apple filed a DMCA takedown. (https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md) Making the Right Choice So what should you do with sourcemaps in your projects? For development: Always enable them. Use fast options, such as eval-source-map in Webpack or the default configuration in Vite. The debugging benefits far outweigh any downsides. For production: Consider your specific situation. But most importantly, make sure your sourcemaps don't accidentally expose secrets. Review your build output, check for hardcoded credentials, and ensure sensitive configurations stay on the backend where they belong. Conclusion Sourcemaps are powerful development tools that bridge the gap between the optimized code your users download and the readable code you write. They're essential for debugging and make error tracking more effective. The question of whether to include them in production doesn't have a unique answer. Whatever you decide, make it a deliberate choice. Review your build configuration. Verify that sourcemaps are handled the way you expect. And remember that proper frontend security doesn't come from hiding your code. Useful Resources * Source map specification - https://tc39.es/ecma426/ * What are sourcemaps - https://web.dev/articles/source-maps * VLQ implementation - https://github.com/Rich-Harris/vlq * Sentry sourcemaps - https://docs.sentry.io/platforms/javascript/sourcemaps/ * Apple DMCA takedown - https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md...

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co