The system behind the craft.
Nov 17, 2025
TL;DR;
We rebuilt Querio’s design system (and a lot of the product with it) to fix inconsistent components, reduce technical debt, and create a more cohesive, maintainable, and delightful user experience. By introducing proper design tokens, using composable primitives, and adding considered motion, we’re moving from spaghetti code to a scalable, consistent foundation that actually feels good to build on — and to use.
Over the last (almost) 2 months, I’ve been working closely with our designer, Mariia, on our new design system — a fresh, sleek new look for Querio. Alongside this, the whole team has been gearing up to release our product rebuild, enhanced with better stability, a more consistent experience and of course, the new design system.
Why Rebuild?
When I first joined, the product rebuild was already underway - at this time it was primarily focused on a rewrite of the python backend to enable greater flexibility and better stability - however, this slowly shifted into an effort to reduce our technical debt and introduce a better user experience.
This decision was made for a few reasons, the core of which being that from my perspective, in joining the company and trying to get along with the codebase - was that it was a mess. There was no true consistency in component design, page implementation, any of it - the frontend was spaghetti code and it was extremely difficult to navigate. For clarity, I say this not to throw shade at any previous design decisions, but more so for maintainability and scale, this was clearly not going to serve us well and we collectively understood this to be the case.
Developer Experience
The main issue we kept running into was mass duplication of code and far too much variance between different components and screens across the app, both visually and functionally.
For example, when I first joined one of the first tasks I worked on was amending a dropdown component. The signature of the component was vast, with a huge number of props that were specifically wired into the specific use case.
Now, this component was used about 10 times across the app, generally for the same purpose - filtering but segmented categories, with search. I was tasked with implementing basic filtering on the ‘Saved’ tab, to filter saved insights by who created them. This component was built upon a custom primitive and accepted props that would handle search functions, selection, on change and various other functions — not aligned to the traditional signatures of a composable primitive.
When making changes to this component, I checked other instances to ensure that functionality across other screens and implementations wasn’t adversely impacted. In doing this, I noticed that there appeared to be another dropdown component serving the same purpose present on other pages. This component looked completely different to the new one, served a slightly different purpose and had a different signature. This example right here was one of the key reasons for why this shift had to happen. We had about 3 components that did roughly the same thing in a non-generic way.
Colours and design tokens were causing issues within the app’s development. We had configured our tailwind config to extend the basic tokens - with definitions derived from our Figma design system (v1). The problem with this extension was that colours weren’t purely semantic and didn’t follow best practices. For example trying to understand what colour was used as a background for components, involved looking through the existing components and trying to notice what background colours were being used, usually something like bg-background-grey-very-light-2 which was horrendous to work with and didn’t really mean anything.
The same went for things like padding, spacing and typography - all of which had loosely defined tokens that would often be overwritten by just using base tailwind classes in components - it wasn’t sustainable and resulted in this disjointed, inconsistent experience for both end users, and developers. In the new system we’ve opted to go for base design tokens (our own colour definitions) and then semantic tokens which reference the base colours. For example, instead of having to use a mangled background grey colour I can now simply use bg-canvas , it’s miles better.
Composability
We opted to use shadcn for our base components, given their excellent composability, good documentation and reliance on radix-ui primitives (which we already had as a dependency). These components become the base foundation for building up composable, consistent components that followed best design practices. We took heavy inspiration from (https://www.components.build/composition) which talks about the benefits to designing components in a way that effectively manages component context, provides good flexibility for introducing variance and separating concerns between different parts of a component.
Take this chat sidebar component for example.
This component is likely only going to be used in one or two places - and will carry the same responsibilities in both. Designing this component is quite simple, we can see three key sections:
Header
Content
Footer / Input
Its function is quite simple, it should accept user input, a function to something with that input, render content in the middle section and have a customisable header. The parent component should handle distributing any state that is required internally, and should maintain a structure that make it useful in other cases (as well as being extensible).
Initially, when the component was first built, it had a huge signature that would accept props managing every state, had poor accessibility and extensive dependencies - it also didn’t work nicely in other situations outside of it’s primary home. So, we rewired this component to have a single parent <ChatSidebar> which owns the following sub-components:
<ChatSidebarHeader/><ChatSidebarContent/><ChatSidebarFooter/>
The parent component can accept a custom context signature, which allows it to distribute state down through the component sub tree, so any state was owned by the parent component and picked up and mutated where it was needed. This visually made looking at the code far more readable, I could see the signature that was being passed into the parent, I could see where it was being used and I could change / extend it where I needed to do so.
One subheading I had originally included in this section was ‘How composable is too composable’ and I figured I would leave that conversation for another time, as we’re still deep in the weeds of exploration…
Motion
Besides providing our customers with a consistent, polished experience - I’ve been pushing for introducing motion into some of components to elevate our UX where appropriate. Earlier this year, I enjoyed working my way through Emil Kowalski’s ‘Animations on the web’ course which focuses upon using animation tastefully, in situations that don’t negatively impact the user’s interaction with the product, but generally provide a more cohesive, fluid experience. The product we’re building fundamentally is designed to be a tool, an extension of a data analyst, product manager, developer or anyone in a business’ workflow to assist them with their daily decision making. This means that Querio needs to feel fast, accurate and considered — and sticking animations on every interaction can negatively impact that perception.
With that being said, some of the animations I’m in the process of designing are crafted in a way that doesn’t interfere with a user’s general interaction with the app, instead accentuates their experience and touches on details, making it feel more delightful. There is a fine line between applying animations to components just for the sake of it, trading off usability for visual aesthetic — it’s a risky business and one that requires proper consideration. Taking from Emil’s work, he’s defined a set of points to think about which seem to work quite nicely for deciding whether or not to animate an experience.
Frequency of use
Animation purpose
Perception of speed
Now, while these seem like common sense when making a consideration for animation — the lines can start to blur especially when deciding what ‘feels’ better and what is right. Take this notebook cell component for example. This component is present across the main features of the Querio experience — it shows a cell and its internals, as well as it’s outputs and some actions. There is already an interaction built into this component - it’s a collapsible, so the user is able to expand and collapse the container and something needs to happen to the contents.

This component will be used frequently, given its presence across the app - and provides the user with some functionality, they can make edits to the code, perform actions on the cell and interact with the output. Look at the two examples below, the first of which is showing the expansion animated linearly, the contents moving from opacity-0 to opacity-100 slightly staggered. It’s important how speed is perceived here given this needs to be a responsive element - allowing for quick interaction. One example here uses linear easing, the other a smooth ease in out curve and finally just an ease out.
Linear
In Out
Out
The question remains, which is the right way to do this — this component needs some animation, even if light touch as we’re transitioning between two states, having no animation would look janky and off.
The linear easing version feels faster because it is, there is no easing curve manipulating the speed of which the box expands, which is reducing the perceived delay of the interaction. That being said, it also feels very mechanical - you get that weird feeling when it abruptly opens and finishes. The in out easing however, is sluggish to start and increases your perception of when you’re able to interact with the component. This needs to be fast, but fluid and considered, which is where ease out comes in… The ease out is snappy, immediately responds to the user interaction, but slows down gently - feeling more fluid and eradicating that horrible janky-ness exhibited in the linear ease.
Something echoed by Emil is this idea that when building for user interaction, the animation should feel responsive first - this generally translates into a much more cohesive feeling experience, without negatively impacting the user’s experience and adding some taste and fluidity to the product.
Here is an example of a component that had more scope to include animations that are more involved - again we’re animating between a changing state. This is our context window indicator component, used in the new Explore experience, which has the sole purpose of showing the percentage of the agent context window which is in use. The idea being that a user can manage their session’s effectiveness based on how full the context window is. This obviously is a micro interaction - but it’s animations like this that can give the user some delight when coming across them, it’s not make or break - but showcases some careful crafting that in turn makes the product feel more polished and considered.
Cohesiveness
I’m very excited for our product release, we’ve spent considerable time carefully crafting Querio from the ground up to feel more fully fledged and cohesive as an experience. We’ve taken the learnings from the previous product and brought them forward into our thinking here - Querio’s vision is to be the definitive data platform for anyone in your company and to do that the product needs to feel well glued together and cater for people of every level of technicality. A huge part of that feeling comes through how the product is experienced on a daily basis by these different user personas. This has deeply influenced our thinking and our design process throughout this product rebuild and it’s something I think will come across clearly in the new product. Not only that, but it has made moving from Figma to code significantly quicker and with far less (if any) variance between designs and production (I can hear Mariia sighing with relief).
Further reading
If you’re interested in learning more about design systems, motion, composability check out these excellent resources which we’ve been internally studying and have given us great insight to date.
Shadcn (Engineer + Design @ Vercel) + Hayden Bleasel (Engineer @ Vercel)
Emil Kowalski (Design @ Linear) https://animations.dev/ https://emilkowal.ski/
Rauno Freiberg (Design @ Vercel) https://devouringdetails.com/
https://rauno.me/craft/interaction-design
Adam Wathan (Creator of TailwindCSS) + Steve Schoger (Designer @ TailwindCSS)
https://www.refactoringui.com/
Fons Mans (Artist + Designer @ Offgrid) https://www.readartifact.com/about

