Shortwave Blog

Shortwave is reinventing what’s possible with email, starting with an inbox that keeps you organized by default

Designing dark mode without the headaches

April 14

5 min read

image

We always knew we wanted to build dark mode for Shortwave, but it didn’t quite make the cut for launch. Post-launch, emails flooded our support inbox with subject lines like “HELP, MY EYES” 😵‍💫🙈 The time to build dark mode had finally arrived!

The project initially felt a little daunting, since a high quality dark mode is much more complex than inverting colors. We needed to consider details like contrast and readability while keeping our design feel on-brand with a completely different theme. Shortwave was designed to be fun, joyful, and calm and we didn’t want to lose that with the introduction of dark mode.

We knew that an efficient approach to dark mode was the key for our small team. We didn’t want to spend the time designing every flow or screen twice and similarly wanted to avoid doubling the engineering effort needed to implement them. Now that dark mode for Shortwave has officially launched (yay!) – I'm excited to share some of the takeaways we learned in the process that helped solve some of our headaches (figuratively and literally).

Double the design without double the work

image
Using the Themer plugin in Figma to convert from light to dark mode in just a few clicks

One of our biggest challenges with dark mode was figuring out how to introduce it to our design system without slowing down our design process (especially because we currently have a design team of one - we’re hiring by the way)! Designing things twice was out of the question and we knew requiring every Figma component to have a dark mode variant would lead to extra upkeep. Our goal was to create an automated system that matched how it actually works in code, with occasional overrides.

Enter Themer — a Figma plugin that creates and swaps themes from published styles. After taking our existing light theme and assigning corresponding dark mode colors, all we have to do to convert a mock to dark mode is run the plugin. It’s magical ✨

So far, Themer has been working great for our team. The only hiccup we’ve experienced is that it can lead to issues with overrides. Converting an instance of a component from Light → Dark → Light, ends up with something that looks like it matches the main component, but all the colors are actually overrides. It hasn’t been a huge issue yet and our workaround has been to only use Themer to convert from Light → Dark and not the other way around.

Name colors based on how they're used, not how they look

image
An example of some of our color naming and pairings in Figma

Naming is hard – naming colors is even harder! We knew a naming system that simply described the hue or value of the colors would quickly get confusing when transitioning from light to dark mode. For example, something named darkGray may make sense in light mode, but would be confusing in dark mode where it would actually be a light gray.

Our solution was to name colors based on semantic usage. Referencing Material Design’s naming approach (see M2 & M3 color guides), our existing light theme consisted of primary, secondary, and accent colors, with variations for text and structural elements such as background, highEmphasis, mediumEmphasis, disabled, and hover.

Some of the color names were intuitive, while others were a bit trickier. Surface colors proved to be the hardest to wrap our heads around. In light mode, they were all whites with different shadow depths, but dark mode needed more variety because you can’t see shadows in the dark. We ended up with four different surface colors including surface, surfaceVariant, surfaceRaised, and surfaceBackground to meet all our needs and then grouped these together in our Figma styles to make them easier to choose from.

With a defined set of colors based on their usage, we were ready to create our light / dark mode pairs needed for the previously mentioned Themer plugin. This naming system not only made creating dark mode a lot easier, but also set a solid foundation for any other theming we decide to add in the future, such as high contrast or custom themes.

Don't be afraid to use your design eye (this isn't a formula)

image
How we expanded some of our brand colors (outlined in white) and tweaked the tonal HSL (Hue-Saturation-Lightness) palettes to be more on-brand (top vs bottom)

One of the easiest ways to extend a single brand color to work across light and dark themes is to make a tonal HSL (Hue-Saturation-Lightness) palette by adjusting the lightness from 100 to 0, making your color go from white to black. This is the general approach Material Design takes as well. There’s even a Figma plugin that will create these for you. These tonal palettes were a great place to start. However, being more mathematically derived, they missed the mark. Design is an art after all, not just a science!

Starting with HSL tonal palettes derived from our brand colors, we manually adjusted some values to better align with our established style guide. We started with slight adjustments to saturation first. If that didn't cut it, we tweaked lightness next and then finally hue. For our blues and purples, we lowered the saturation to maintain our calmer palette. Our yellows were changed most drastically, as we adjusted hue, saturation and lightness across the palette to keep it cheery and avoid muddiness. Although small, these subtle tweaks make a huge difference in conveying the same mood across light and dark modes.

The colors in these reference palettes were the source of all our color variables, acting as design tokens to define all of the semantic colors used in our themes. For example, our secondary color variable is purple30 (light mode) and purple40 (dark mode) from our reference palette. This type of system makes it easier to make fine-tune adjustments across your entire app and themes because everything points back to a single cohesive set of color palettes.

Speak the same language as engineering

With a relatively small team and a lot to get done, we needed to make the workflow between design and engineering as seamless as possible. To do this, our design approach for dark mode needed to match how it’s actually built in code as much as possible.

Thankfully, long before we started building dark mode, our proactive engineering team made the decision to use styled-components and its theming functionality. Similar to our design approach in Figma, we leveraged this theming functionality instead of having to build a dark mode variant for each component. This meant our code worked similarly to the Themer Figma plugin, where colors could be light and dark mode aware.

image
Colors are light and dark mode aware with styled-components theming functionality

We also introduced CSS variables (aka CSS custom properties) to our codebase to allow easier referencing between designs and code. Browsers do a great job of exposing CSS variables, allowing you to see them when you inspect elements in the developer console and even easily switch from one variable to another.

image
CSS variables are a lot easier to inspect and verify in the browser for engineers and designers alike

Mapping our design and engineering systems together saved everyone time and kept our cross-functional team aligned. Engineers could verify that they were using the right color variable in Figma, and I could double check that the correct color was being used during code reviews. Everyone was on the same page!

Introducing dark mode to Shortwave

While this project was far beyond “switching out a few colors,” all of the hard work designing dark mode was more than worth it. We now have a system in place that allows dark mode (and any other future themes) to easily grow with Shortwave and its new features — without needing extra design and engineering work.

We officially shipped dark mode to users last week and we’ve loved watching everyone find yet another reason to enjoy their inbox with Shortwave!

We're hiring

Ready to take on fun projects like dark mode with the Shortwave team and enjoy other awesome perks like flexible remote work, competitive compensation, and regular offsites (places like Hawaii, Miami, and New Orleans)? We are hiring designers and engineers. Check out our open positions and apply today!

📣 Join us live on Thursday, April 21st at 2PM PST

Get your questions answered directly by our team during a live tech talk on Twitter (set a reminder). Send us your questions in advance with #AskShortwave or chat with the team live.

Read more
Introducing Shortwave: Actually Enjoy Your Inbox

February 15

5 min read

image

Today, we’re launching Shortwave, a brand new experience for your Gmail account. Shortwave helps you email smarter and faster, so you can not only be more productive, but actually enjoy your inbox. You can sign up for Shortwave today and get started for free.

Let's face it – your email isn’t working for you

You aspire to hit Inbox Zero, but in practice emails just fall off the end of your inbox. Were you supposed to respond to that? Too late now! I guess they’ll text you if it mattered. You’ve tried priority inbox, "splits", filters, and every other setting you can find. You’ve memorized keyboard shortcuts and fly through your inbox mashing "e", barely reading the content. More. Faster. You’ve got this.

Except you don’t "got this". Inbox Zero remains elusive. You’re tracking so much in your head. Which emails are urgent? Which ones do you still need to respond to? You have an ever-growing number of SaaS notifications. Long threads are hard to follow. Did someone reply privately in this group thread? Did an attachment get dropped when they added you?

You’re drowning.

Email smarter, not just faster

The problem isn’t you – it’s your email client!

Shortwave brings order to the chaos. We’ve completely redesigned email from the ground up so you can stay calm, focused, and in control.

Shortwave treats email like the to-do list that it really is, so you don’t have to track everything in your head. Pin urgent emails to the top of your inbox in one click. Re-order threads to stay prioritized. Stack and label related threads to keep them together. Inbox Zero might be impractical every day, but Inbox Organized happens without breaking a sweat.

Our secret weapon is called bundles. They automatically group related emails and let you act on them all at once. Old calendar notifications? Gone. Asana updates? Bam. Newsletters? Oh wait, you do want those: snooze the whole bundle until your flight with one tap.

image

Bring clarity to complex threads

Long group email threads can be tricky. When were people added and removed? How many different conversations are really going on? What happened before you were added? Shortwave’s clear and compact thread UI shows you what you need to know, and nothing else.

image

Write beautiful emails

Shortwave helps you communicate clearly and intelligently without sacrificing speed. With mentions, you can modify recipients without taking your hands off your keyboard. Markdown text shortcuts make it easy to format your messages, and our Giphy and emoji support let you add some flair. We’ve optimized for common actions, like referencing a recent thread, finishing a draft, quoting text, or adding a photo.

image

Work together

Shortwave is great for teams. At work, you email external people all the time: customers, vendors, job candidates, and more. Many of these threads are important to your teammates too, and with Shortwave it’s easy to loop in a single colleague or your whole team.

When collaborating on a thread, you can see who’s online and who’s typing, making it easy to know when to jump in. Emoji reactions let you respond quickly and have a little fun too. Unlike Gmail, if you were added later on, we show you the previous messages in their entirety, including attachments, inline images, formatting, and recipients.

We’re bringing channels to email. Channels are topic-based spaces where you can share threads with your team. Start a new thread to have a thoughtful discussion. Add an existing thread into a channel to share that thread – including its history – with your team, so they can follow along, respond, and see it in their search results. Channels let you unify both your internal and external communications into a single inbox, so it’s easier for everyone to stay caught up.

image

To get started with your team, just create a workspace. They’re free for up to 10 members and paid plans are just $9 per person per month.

We’re just getting started

When I started Firebase in 2011, serverless apps were a radical new idea – today, they’re standard practice. It took years for us to win people over, but seeing early adopters fall in love with Firebase gave us the conviction we needed. We’re already seeing the same early signs with Shortwave. Email is making a comeback.

"Shortwave's UX feels fast and joyful! It makes email less of a chore and lets me focus on my actual work."

Phillip Wang, CEO, Gather

It’s going to be a long journey, but we’re prepared and resourced for it – and we won’t stop until email is hands-down the best, easiest, and most enjoyable way for you to communicate online (and not even then). To set our company up for this long-term mission, we’ve built an exceptional team, including many early Firebase employees. We’ve also partnered with some of the smartest investors in the world. We raised a $9M Series A led by Union Square Ventures and Lightspeed Venture Partners, with participation from Flybridge, James Tamplin, Immad Akhund, Peter Reinhardt, Oliver Cameron, and others.

We had a lot of fun building Shortwave, and we hope you enjoy your new inbox! To get started with Shortwave, all you have to do is sign in with your Gmail account or check out our quickstart guide to learn more.

We’d love to hear your feedback at feedback@shortwave.com or on Twitter.

Happy emailing!

Read more
Real-time React Apps Using Watchables

January 6

5 min read

Have you ever needed to build a user interface in React that updates in real-time based on server events? We struggled to find a good pattern that made this easy in our app. After trying to do this using Redux, we eventually found a much better way. In this post I’ll document our journey and our (open source!) solution.

Shortwave’s real-time UI

A major goal of Shortwave is to provide a much more real-time email experience. Users should see new emails right away without needing to click to refresh, and triage actions taken on one device should update other devices (and tabs) immediately.

To accomplish this, our apps have a websocket connection that incrementally syncs down data from our backend. We store that data locally and merge it with local state based on user actions, so that we can compensate for latency and give users a responsive app even when their network isn’t.

Our apps have client-side logic in TypeScript for managing this local state, including handling asynchronous server updates, user input, disk persistence, and other business logic. To make our user interface work, however, we need to get this state into our React components.

Why Observables didn’t work

Our first attempt at doing this was to just expose the state as an Observable to get it into React. Let’s take an example of our draft service - which has an interface like this:

interface DraftService { watchDraft(draftId: DraftId): Observable<Draft>; watchAllDrafts(): Observable<Record<DraftId, Draft>>; }

Usage of our service looks something like this:

const DraftPreview: React.VFC<{draftId: DraftId}> = ({draftId}) => { const service: DraftService = useDraftService(); const [draft, setDraft] = useState<Draft | null>(null); useEffect(() => { const observable = service.watchDraft(draftId); const subscription = observable.subscribe(setDraft); return () => subscription.unsubscribe(); }, [service, draftId]); return draft === null ? 'Loading...' : `Draft: ${draft.subject}`; };

This worked, but we quickly noticed a problem. The first render pass in React always resulted in displaying the loading state to the user. Even if we had a draft loaded in memory and we could display it instantly, we had to wait until the useEffect hook ran to update the state. For toy apps, this isn't noticeable because useEffect can run and React can rerender the component before the browser has the chance to paint. Our application was large enough that there was flickering of the loading state every time a component was mounted.

image

Why Redux didn't work

Our fix for flickering? Put the state into Redux! So now our app had a hook like this at the top of the component hierarchy:

function useSyncDraftsIntoRedux() { const service = useDraftService(); const dispatch = useDispatch(); useEffect(() => { const observable = service.watchAllDrafts(); const subscription = observable.subscribe( (drafts) => dispatch(setDraftsAction(drafts)) ); return () => subscription.unsubscribe(); }, [service, dispatch]); }

This lets us fix our component’s flickering while simplifying it at the same time. Great!

const DraftPreview: React.VFC<{draftId: DraftId}> = ({draftId}) => { const draft = useSelector( (state: Store) => state.drafts.drafts[draftId] ); return draft == null ? 'Loading...' : `Draft: ${draft.subject}`; };

What’s not to love? Well, a lot it turns out! First of all, it's not clear when to load data in this model. In our draft example we can start piping them all into Redux at app load time, but we can't do this with all of our data. We have to manually set up the sync into Redux anytime we display the data anywhere in the app - a cumbersome and bug-prone pattern. We also ran into performance issues early on and needed to optimize our selectors with tools like Reselect.

Additionally, we have all our state duplicated into two places - first the service and then the Redux store. Not only did this make it difficult to figure out where the source of truth for our state was, it also required a bunch of extra code. We needed to create reducers to handle the state and actions so we can wire up the service to the store. We also were doing this before Redux Toolkit was production ready, which just meant even more boilerplate code to write.

Overall our use of Redux felt like overkill and imposed a very rigid structure to our code - all we needed was a way to expose our application state to React!

Watchables to the rescue

We wanted a simpler solution. We liked the simplicity of the observables pattern, but observables are designed for streams of data and don’t necessarily have a “current” value. We needed to synchronously access state for our first render, so what we wanted was a data structure that both holds a current value and has a notification mechanism for when it’s updated. Enter Watchables - a small data structure we built for exactly this purpose - to expose a value into React. At its core, Watchables have a small API that looks something like:

/* A readonly value that can be watched. */ interface Watchable<T> { /* If a watchable has a value or is empty (a loading state). */ hasValue(): boolean; /* Access the current value. */ getValue(): T; /* * Watch for updates to the value. * Will initially be fired with the current value if there is one. */ watch((value: T) => void): Unsubscribe; }; type Unsubscribe = () => void; /* A mutable watchable value. */ interface WatchableSubject<T> extends Watchable<T> { update(value: T): void; }

We can now update our component to look something like the following:

const DraftPreview: React.VFC<{draftId: DraftId}> = ({draftId}) => { const service: DraftService = useDraftService(); const watchable = useMemo( () => service.watchDraft(draftId), [service, draftId] ); const [draft, setDraft] = useState<Draft | null>( watchable.getOrDefault(null) ); useEffect(() => watchable.watch(setDraft), [watchable]); return draft === null ? 'Loading...' : `Draft: ${draft.subject}`; };

Watchables also have some other nice properties - they allow for an empty loading state, frequently updated values can be snapshotted, and you can do memoized state transformations. Combining these with a small set of hooks allowed us to simplify our component even more:

const DraftPreview: React.VFC<{draftId: DraftId}> = ({draftId}) => { const service: DraftService = useDraftService(); const draft = useMemoizedWatchable( () => service.watchDraft(draftId), [service, draftId] ); return draft === null ? 'Loading...' : `Draft: ${draft.subject}`; };

We now use Watchables all over in our application, for loading and displaying messages, drafts, contact information, and online presence status. It's become a fundamental part of our application and has helped us to simplify our architecture and define clear boundaries between our business logic and user interface.

As part of this blog post, we’ve open sourced our implementation of Watchables along with a small set of hooks at github.com/shortwave/watchable. Adding it to your application is as simple as npm install --save @shortwave/watchable. More information can be found on GitHub. If you found this post interesting, check us out - we're hiring!

Read more
Email: The Future of Messaging

September 21, 2021

4 min read

image

Last year, a team of ex-Firebasers and I started Shortwave, a stealth startup we’re unveiling today. With Shortwave, we’re placing all of our chips on one big bet: email will dominate the future of messaging.

We believe that email, despite all of its flaws, will eventually win as the way we communicate online, displacing iMessage, WhatsApp, Slack, WeChat, and every other messaging app in your pocket today.

At this point, you may be thinking “Email?! Isn’t that the past?”, and you wouldn’t be alone. The email ecosystem has stagnated as the world changed around it. Email inboxes are overwhelmed with a volume of automated messages they were never designed to handle. Email clients — hamstrung by decades-old protocols and UI concepts — have failed to take advantage of mobile. Meanwhile, messaging apps have raced ahead with real-time updates, native support for groups, easy sharing of photos and videos, end-to-end encryption, and more.

However, email still has two key advantages that outweigh all of its flaws:

  1. It’s universal — With nearly 4 billion users, email is the most ubiquitous messaging technology and the most reliable way to communicate with just about anyone.
  2. It’s open and decentralized — Developers can build interoperable services without asking anyone for permission. This allows users to choose from a wide variety of clients and hosting providers, as well as a flourishing ecosystem of tools built on top of the protocol (for sending newsletters, wedding invitations, invoices, and more). Most importantly, if you’re unhappy with your provider, you can switch to another one or even run your own servers.

Messaging apps, on the other hand, are closed services that don't interoperate. You can’t send messages between Slack and Teams or from WhatsApp to iMessage, so you end up juggling a dozen apps to stay connected. These apps are controlled by a select few companies that mandate the use of their own clients, force all network traffic through their servers, and restrict what services can be built on top of them. These centralized services also create a single point of failure and control, making them unreliable platforms for free expression when it matters most. If you’re unhappy with your experience, too bad.

The future of online communications cannot be trusted to a centralized service. It must be built on a foundational technology that is:

  • Universal — I should be able to talk to anyone in the world from a single app.
  • Open & decentralized — I should be able to develop and run my own clients, servers, and services.
  • Intelligent — I should never miss an important message, no matter how many messages I receive in a day.
  • Flexible & expressive — I should be able to communicate the way I want, whether that means writing a long memo or sending a quick emoji reaction to a friend’s video.
  • Private & secure — I should feel confident sending even my most sensitive information.

The right choice isn’t to invent a new protocol. The right choice is to build on email — a technology that is already universal, open, decentralized, and battle-tested for over 40 years.

While email today does not yet satisfy all of our requirements, recent advancements in machine learning, encryption, and decentralized governance have made some of email’s most challenging problems far more tractable. People and businesses are also reconsidering social conventions around email and messaging due to the shift to remote work, making this an ideal time for a new approach. With the right investments in modern clients, new servers, upgraded protocols, and thoughtful design, it’s now possible to build a user experience that lives up to email’s potential.

Upgrading email will be a difficult, multi-year journey, but we have the right team, resources, and determination to play the long game. Our first product is a brand new inbox experience for your existing Gmail account. It is still being tested privately, but we are providing early access to a select set of individuals and companies. If you would like to try it out, sign up here. If our mission excites you and you want to help build the next wave of communication, we’re looking for great people to join us, so please get in touch.

Read more

Sign up for our newsletter

Hear about the latest updates and opportunities with Shortwave!