Stealth Mode North Star — Rebranding in secret with feature flags

A presentation at React Brussels in October 2022 in Brussels, Belgium by Kathleen McMahon

Slide 1

Slide 1

Stealth-mode North Star Rebranding in secret with feature flags — Kathleen McMahon, Senior Design Systems Engineer

Welcome, everyone! I’m Kathleen McMahon and I’m here today to show how you can roll out a product rebrand — in secret — using feature flags.

Before we begin, let’s get some details out of the way.

Slide 2

Slide 2

Thank you organizers

First, I want to give a huge thank you to Omar, Aymen, the speakers, the event production crew, the volunteers, and everyone that worked so hard to create an excellent event!

Slide 3

Slide 3

https://noti.st/resource11

My presentation will be posted on Notist, that’s https://noti.st/resource11, after my talk.

Slide 4

Slide 4

@resource11 Twitter | Instagram | GitHub

You can follow me at. Resource11 on Twitter, Instagram, and GitHub.

Let’s back up so I can introduce myself better…

Slide 5

Slide 5

Kathleen McMahon | Engineer • Designer • Speaker

So I’m an engineer and a designer and I like to speak about things…

Slide 6

Slide 6

image: Me on a cyclocross bike, hopping over barriers and riding through mud.

And… I sometimes race cyclocross. Very badly.

Slide 7

Slide 7

image: Me on a cyclocross bike, in Spam, Ninja Turtle, Nerds candy box, Medusa costumes

Mostly you’ll see me in costume, racing two laps to your six, at the back of the pack, on a singlespeed bicycle — with a bunch of onlookers laughing at me, toasting with their beverages.

Slide 8

Slide 8

image: Stanley Spadowski shouts "OPEN WIDE!" and douses boy with a fire hose

Now I think y’all agree these past few of years have been intense. Right?!

Slide 9

Slide 9

image: Group of penguins gathered on an iceberg with a few swimming and jumping in the ocean. One penguin attempts to jump atop an iceberg to join their penguin friends — and fails — splashing back into the water in ungraceful fashion.

See the penguin in the middle of the screen? The one that failed to jump on the iceberg? That was me. Burning out. Surviving Covid layoffs. Tech interviews. New jobs. The isolation of lockdown. More burnout. So.. I did a few things to survive. Like…

Slide 10

Slide 10

image: a wide-screen view of the colorful lamps and lighting strips adorning my home office desk and bookcases

Spending my time getting crafty with lighting setups during the colder months.

Slide 11

Slide 11

image: Thor, my floofy beige-striped cat, rests in his bed in my colorfully-lit office

Photographing my cats Thor…

Slide 12

Slide 12

image: Thor, resting on the windowsill of my kitchen window, enjoying the warm summer breeze and sunshine on his soft beige fur and dreamy blue eyes.

Slide 13

Slide 13

image: Otis — my silky black haus panther — nestled between my purple bed pillows, gazing at me with calm golden eyes.

…and Otis…

Slide 14

Slide 14

image: Otis and Thor — in perfect light and dark fur contrast — nestle together atop a soft red flannel throw.

As this photo of them snuggling clearly shows… they are always fighting. It’s a problem.

Slide 15

Slide 15

image: A wide-planked boardwalk — with soft sand and sea grass surrounding its weathered wooden railing — leads toward the calm ocean during a cloudy afternoon low tide

And in the summer, I go to the beaches!

Slide 16

Slide 16

image: Lush green foliage surrounding fiberglass boardwalk leading to the ocean at dawn. The bright orange "Public access 1" sign accenting the way to ocean bliss.

At different times…

Slide 17

Slide 17

image: Weathered wooden railings surround a raised boardwalk leading to an expanse of beach at low tide. Two individuals sit in beach chairs nearby glancing at the calm ocean water.

Different days, and…

Slide 18

Slide 18

image: Weathered beige wooden railings and tall sea grass surround soft light beige sand leading to a calm surf at peak low tide. The late morning sun reflecting off of wispy cirrus clouds.

Different locations…

Slide 19

Slide 19

image: Dark gray weathered wooden railing and tall sage green sea grass surround a fiberglass boardwalk leading to a deep blue ocean at low tide in the late afternoon. Two fluffy white clouds sail across the clear blue sky.

Always at low tide…

Slide 20

Slide 20

image: Calm waves lapping against the sand

Which is the best time…

Slide 21

Slide 21

image: Dark beige wet sand, ready to reveal a baby sand dollar

…to look for sand dollars

Slide 22

Slide 22

image: A sand dollar peeking out of a cluster of rocks, barnacles, and periwinkle shells in a crowded ocean tidal pool.

It’s like the Where’s Waldo of the ocean.

Slide 23

Slide 23

image: A montage of sand dollars of various sizes and colors, next to a tiny and medium sand dollar in the palm of my hand.

So I get a serious…

Slide 24

Slide 24

image: A tiny pearlescent shell sits on the side of a dark gray sand dollar in the palm of my hand. Beneath my hand is the cool waters of a very crowded tidal pool full of rocks, shells and ocean water.

Collection of sand dollars!

Slide 25

Slide 25

image: A gathering of cream-colored sand dollars and a large periwinkle shell in a large open clam shell sitting atop a brown wooden bench at the beach

And I photograph

Slide 26

Slide 26

image: A gathering of a dark gray sand dollar, large and small periwinkle shells in a large open clam shell sitting atop a weathered gray wooden bench at the beach

My treasure

Slide 27

Slide 27

image: A gathering of sand dollars of many colors and sizes in a large open clam shell sitting atop the sand next to some seaweed at the beach

Every time

Slide 28

Slide 28

image: A gathering of sand dollars of multiple shapes, sizes and colors on my dining room windowsill.

Trying to do this at home is a problem… because of the Otis factor.

Slide 29

Slide 29

image: Otis — black cat of magic — lovingly gazing at me with golden eyes while lying upside down. Paws outstretched.

Because Otis is especially needy…

Slide 30

Slide 30

image: A gathering of green aventurine, sodalite, rose quarts towers, and quarts diamond on the kitchen table

See, during lockdown before burnout hit peak, I also started a heck of a crystal collection. And attempted…

Slide 31

Slide 31

image: Otis, the black wonder cat, steps into the frame of my crystal collection

To get good photos. But Otis was having none of that…

Slide 32

Slide 32

image: Otis' paws walk through my crystal arrangement on the kitchen table

None…

Slide 33

Slide 33

image: grouping of citrine, amethyst, and celestite glowing in the sunshine-washed windowsill

…of it.

Slide 34

Slide 34

image: Otis — sassy black cat — sits exactly on top of all my windowsill crystals, seeking attention.

None of it.

Slide 35

Slide 35

image: A gathering of sand dollars of multiple shapes, sizes and colors on my dining room windowsill.

So my attempts at this…

Slide 36

Slide 36

image: Otis — always insistent black cat — standing on my dining table, inspecting my gathering of sand dollars on the dining room windowsill.

Yeah… nope.

Slide 37

Slide 37

image: Thor — ever curious fluffy beige Manx cat of Wonder — standing on my dining table, inspecting my gathering of sand dollars on the dining room windowsill.

And now he’s even taught Thor…

Slide 38

Slide 38

image: Thor bravely places his white-mittened beige paw on the windowsill gathering of sand dollars.

To do…

Slide 39

Slide 39

image: In pure art director fashion, Thor sits distinctly on one windowsill sand dollar. Ready to provide his ultimate design judgement..

The same…

Slide 40

Slide 40

image: Thor looks up from the windowsill to the camera with a very satisfied look. His blue eyes gaze hypnotically my way as his pink nose points upward, whiskers pridefully twitching.

Thor looks pretty satisfied.

Slide 41

Slide 41

image: Otis looking very cute as he lays upside down on my bed, gazing at me.

Who could resist Otis though? He’s the reason I made that intro video that BeJS shared out on Twitter. He wanted to be here.

Slide 42

Slide 42

image: Otis expectantly gazing at me from his cat tree perch.

So here is Otis. Attending by proxy.

Slide 43

Slide 43

image: Penguin — running forward on an icy tundra — skids to a stop.

Anyhow… When it was time to head to Brussels to see people in person, I felt so excited! People. Finally!

Slide 44

Slide 44

image: Group of penguins on tundra running away

Then I remembered… omg… talking to people.

How many of you felt the same? My introvert did a little panic…

Slide 45

Slide 45

image: Penguin — running forward on an icy tundra — skids to a stop.

But excitement won out, y’all!

And now I’ve shaken off my stage fright anxiety…

Slide 46

Slide 46

A story…

Thanks for your patience.

So… you’re here to learn about secret rebrands…

I’ll tell you a story about that.

Slide 47

Slide 47

Northwestern Mutual

Currently, I’m working with an amazing group of people at Northwestern Mutual as a Senior Design Systems Engineer, helping them take the Luna Design System to the next level.

Slide 48

Slide 48

LaunchDarkly

Before working at Northwestern Mutual, I was on the UX Next team at LaunchDarkly. This story is about my time there.

The UX Next team enabled other designers and engineers to craft high-quality experiences for their customers.

Wow, that sure is some corporate speak, huh?

Slide 49

Slide 49

Maturing the design system

When I first joined LaunchDarkly, part of our work included maturing the design system.

Slide 50

Slide 50

Design Systems are ALWAYS the hotness image: Person in inflatable dinosaur costume flips into a raft floating on a pond and claps with glee.

And I’m a super fan of design systems, so it was exciting!

Slide 51

Slide 51

image: Visual of the various intersecting parts of a design system.

If you’ve ever worked on a design system, you know there are a lot of things to consider. Which can sometimes feel like…

Slide 52

Slide 52

image: Woman attempting to arrange 10 squirming kittens in a straight line, with varied success

…herding kittens. Always fun, yet lots of moving parts. Similar to herding kittens, maturing a great design system can be tough with so many moving parts. If your squad is small, and not a dedicated system team you have to be very strategic and choose what to tackle. For example…

Slide 53

Slide 53

Foundations missing

In our case, the foundations were missing from our system. We had components, but none of the building blocks, so we began our journey to define them.

Slide 54

Slide 54

Design tokens

Like design tokens.

Naturally, we were just beginning to start re-defining our tokens when…

Slide 55

Slide 55

In an unexpected twist, someone kicked in the door wearing a Big Bird costume!…

Slide 56

Slide 56

Hello… brand refresh.

Not really. The creative team entered the scene and announced a brand refresh.

Slide 57

Slide 57

image: LaunchDarkly brand visual assets

So there we were, presented an updated brand North Star!

Slide 58

Slide 58

What is a brand North Star?

You may ask… what is a brand North Star, Kathleen?

Slide 59

Slide 59

There is this article by M81 Creative that gives a great explanation of North star:

“Your brand’s North star is the promise that you are making to your customers.”

Slide 60

Slide 60

At LaunchDarkly, their North Star aligns with their core message:

Software powers the world. LaunchDarkly empowers all teams to deliver and control their software.

Slide 61

Slide 61

image: LaunchDarkly brand visual assets

They lean into this mission with their colors, typography and signature logo.

Slide 62

Slide 62

Creating a strong brand takes time

Now creating a brand North Star takes time to do right

You have to do things like strategy, competitive analysis, user and customer insights, and creative explorations

This process take up you know many months, sometimes up to a year.

At that time, you will have some key outputs like logos, colors, typography and such.

Slide 63

Slide 63

Creating a strong brand takes time (continued)

Once those branding visuals are established, it’s time for teams to roll that North Star into the marketing site and the product app.

Slide 64

Slide 64

Now it gets tricky.

This is where it gets tricky.

While our team was informed about the brand refresh at the earliest possible moment, we had one wrinkle. And was a big one.

Slide 65

Slide 65

Time to customer rollout: 13 weeks

The time to the rebrand rollout to customers was in 13 weeks

Slide 66

Slide 66

It was a secret project…

And it was a secret project,

Slide 67

Slide 67

It was a secret project… for most of the company

…for most of the company.

Slide 68

Slide 68

Time to internal rollout: 10 weeks

That meant the time to internal rollout to our teams was 10 weeks

Slide 69

Slide 69

image: Ross from Friends repeatedly yells "pivot!" as Rachel and he maneuver a large chair up an awkward staircase.

So we had to carefully pivot to figure out how to get things done, since…

Slide 70

Slide 70

image: LaunchDarkly marketing site in 2014

Typically, visual refreshes happen more often on the marketing side

Slide 71

Slide 71

image: LaunchDarkly marketing site in 2015

Slide 72

Slide 72

image: LaunchDarkly marketing site in 2018

Slide 73

Slide 73

image: LaunchDarkly marketing site in 2019

Slide 74

Slide 74

image: LaunchDarkly marketing site in 2020

Slide 75

Slide 75

image: LaunchDarkly marketing site in 2021

Slide 76

Slide 76

Product visual refresh: less often

And visual refreshes happen less often in the product app because…

Slide 77

Slide 77

Rebrands impact established product roadmaps.

Slide 78

Slide 78

Outdated product visuals break brand promises

And you need to be careful when the visuals between your marketing site and product app get out of sync.

First, your product app will start looking outdated and

Second, outdated app visuals break your brand promise.

Slide 79

Slide 79

image: Mars Babycat sleepily squints amid a cozy comforter

So our team regrouped and thought… how can we get this done?

Slide 80

Slide 80

image: Mars Babycat sleepily squints amid a cozy comforter (still)

And how do we do this without making any questionable decisions that may impact the product, because you don’t want to be making questionable decisions like I don’t know…

Slide 81

Slide 81

image: Shrimp and lime jello mold, plated with apple slices

Making dishes that include jello and shellfish?

It’s just not a good look.

But then… we had an idea.

Slide 82

Slide 82

image: Zootopia's Flash — the best sloth ever — slowly breaks into a grin while Judy Hopps and Nick Wilde patiently wait for information

Why not… use our own product to roll out a rebrand?

Slide 83

Slide 83

Feature flags! This means that…

Slide 84

Slide 84

image: Three baby raccoons nestled inside a tree trunk

We could set up a flagging structure and…

Slide 85

Slide 85

image: Three baby raccoons nestled inside a tree trunk... with a calico cat disguising themself as a sibling

…rebrand in stealth mode.

We could make all of our changes alongside other teams, not disrupt other squads roadmaps.

All while the other squads were not aware that the product brand refresh was actually happening.

Slide 86

Slide 86

What is a feature flag?

You may be asking… what is a feature flag, anyway?

Slide 87

Slide 87

There is this article by Ian Buchannan about Feature Flags and it states

“Feature flags (also commonly known as feature toggles) is a software engineering technique that turns select functionality on and off during runtime, without deploying new code.”

Slide 88

Slide 88

In JavaScript, it may look something like this.

if (featureFlags[‘new-cool-feature’] === true) { renderNewCoolFeature(); }

Slide 89

Slide 89

How did we use flags?

So what did we use flags for in this case?

Slide 90

Slide 90

Boolean flags Prerequisite flags Multivariate flags

Well, we used three types of flags.

Boolean, prerequisite, and multivariate flags

Slide 91

Slide 91

Well, we used them to control:

Theming, data attributes, fonts, design tokens, HTML templates, and UI updates.

Slide 92

Slide 92

And! Since we had to do it all in 10 weeks…

Slide 93

Slide 93

In secret…

Slide 94

Slide 94

image: the Ruling Ring of Isildur's Bane

We used this one ring…

Slide 95

Slide 95

image: the Ruling Ring of Isildur's Bane

…ahem!

Slide 96

Slide 96

image: the Ruling Ring of Isildur's Bane

One flag as the basis of most of our changes.

Slide 97

Slide 97

Feature Flag: enable-2021-rebrand

The flag that all our other flags were dependent upon was the enable-2021-rebrand flag

Of course, in retrospect, we could have named our flag a bit differently. Anyone in our company that looked up our flag name in our LaunchDarkly UI probably had some idea of what we were working on.

Slide 98

Slide 98

image: Enable 2021 rebrand flag targeting view

And this flag was a prerequisite for other flags.

Slide 99

Slide 99

image: Prerequisite flag toggled off, wrapping a group of four feature flags toggled on

Prerequisite flags are great for testing out additional features in isolation

Slide 100

Slide 100

image: Prerequisite flag toggled off, wrapping a group of four feature flags toggled on

This means you can have all these sub flags turned on.

But unless the prerequisite flag is on…

Slide 101

Slide 101

image: Enable 2021 rebrand flag targeting view

…the other flags wouldn’t work

Slide 102

Slide 102

image: Prerequisite flag toggled on, wrapping a group of four feature flags — two toggled on, two toggled off

A prerequisite flag allowed us to test individual features…

Slide 103

Slide 103

image: Prerequisite flag toggled on, wrapping a group of four feature flags — three toggled on, one toggled off

In various combinations without impacting the rest of the app.

Slide 104

Slide 104

image: Enable 2021 rebrand flag targeting view

And because… secret project, we could target our stakeholders to share a preview of our work.

Once this flag was on, we could continue work on things like…

Slide 105

Slide 105

Theming support using…

Slide 106

Slide 106

Data attributes ● Data attributes…

Slide 107

Slide 107

Data attributes: data-theme=“osmo" data-theme=“legacy”

…to control whichever theme we wanted to serve up.

Slide 108

Slide 108

New font family: Inter

But we also wanted to use our new font, Inter, in our visual refresh so…

Slide 109

Slide 109

image: Enable Inter flag targeting view

We added the enable-inter flag inter with the rebrand flag as a prerequisite…

Slide 110

Slide 110

Data attributes: data-theme=“osmo" data-theme=“legacy" data-inter=“true”

Now that our font flag was set up, we added data-inter to our set of data attributes so we could test fonts independently within our rebrand theme.

Slide 111

Slide 111

Then, we paired those data attribute values with React Context…

Slide 112

Slide 112

And hooks…

import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from ‘react’;

type Theme = ‘legacy’ | ‘osmo’;

export type ThemeContext = { theme: Theme; };

export const ThemeContext = createContext<ThemeContext>(null as unknown as ThemeContext);

export function ThemeProvider({ theme, children, isInterEnabled, }: { theme: Theme; children: ReactNode; isInterEnabled: boolean; }) {

useEffect(() => { document.documentElement.setAttribute(‘data-theme’, theme); }, [theme]);

useEffect(() => { if (isInterEnabled) { document.documentElement.setAttribute(‘data-inter’, ‘true’); } else { document.documentElement.removeAttribute(‘data-inter’); } }, [isInterEnabled]);

const contextValue = useMemo( () => ({ theme, }), [theme], );

return <ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>; }

export function useTheme() { return useContext(ThemeContext); }

Slide 113

Slide 113

…to create a theme context

type Theme = ‘legacy’ | ‘osmo’;

export type ThemeContext = { theme: Theme; };

export const ThemeContext = createContext<ThemeContext>null as unknown as ThemeContext);

Slide 114

Slide 114

...and a theme provider that we could use anywhere we need in our app,

export function ThemeProvider({ theme, children, isInterEnabled, }: { theme: Theme; children: ReactNode; isInterEnabled: boolean; }) {

const contextValue = useMemo( () => ({ theme, }), [theme], );

return <ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>; }

Slide 115

Slide 115

We created this useTheme function for wherever we wanted use a theme.

export function useTheme() { return useContext(ThemeContext); }

Slide 116

Slide 116

And used our theme provider in the app entry point, leveraging our rebrand flag value to set the theme

const theme = is2021RebrandEnabled() ? ‘osmo’ : ‘legacy’;

Slide 117

Slide 117

And pass the theme into the Provider

<ThemeProvider theme={theme} isInterEnabled={isInterEnabled()}>

Slide 118

Slide 118

Which uses the useEffect hooks to set data attributes in the root element of our app.

useEffect(() => { document.documentElement.setAttribute(‘data-theme’, theme); }, [theme]);

useEffect(() => { if (isInterEnabled) { document.documentElement.setAttribute(‘data-inter’, ‘true’); } else { document.documentElement.removeAttribute(‘data-inter’); } }, [isInterEnabled]);

Slide 119

Slide 119

Data attributes: data-theme=“osmo" data-theme=“legacy" data-inter=“true”

Now that app theming was scaffolded, we could continue work on…

Slide 120

Slide 120

Design tokens. So like I mentioned, before the North Star was revealed, our team was already refactoring our token architecture.

Once this rebrand was announced, we had to adjust our tokens work without losing any of our progress.

And we didn’t have time to automate the process with Style Dictionary.

Slide 121

Slide 121

Design Tokens are an abstraction for everything impacting the visual design of an app/platform. — Sönke Rohde

If you’ve never heard of design tokens, Sönke Rohde Tweeted a great definition: Design Tokens are an abstraction for everything impacting the visual design of an app or platform.

Slide 122

Slide 122

Design Tokens are design decisions propagated through a system. — Nathan Curtis

In other words, design tokens are design decisions propagated through a system.

Slide 123

Slide 123

Base tokens —color-blue-500: hsl(231.5, 100%, 62.5%);

Semantic tokens —focus-color: var(—color-blue-500);

Component tokens —Button-color-text-focus: var(—color-blue-500);

Generally, tokens span three categories:

Base, the raw values. Semantic, aliased to base values and shared across the application, and component tokens, scoped at the component level.

Slide 124

Slide 124

Blended base + semantic tokens

Our legacy token system was a blend of base and semantic…

Blended base + semantic tokens —primary-green-base: hsl(165.1, 72.5%, 45.7%);

Slide 125

Slide 125

Not Scalable

…so it wasn’t scalable, due to the naming conventions used.

It didn’t make sense to have a color name in a semantic token if our primary color changed. Hence the refactor.

Considering the 10-week time frame…The tokens that we were most concerned about were our application-level tokens.

Slide 126

Slide 126

We chose to focus on using the data attribute to map our application-level tokens to base tokens in either the legacy or osmo theme.

Since our theme was controlled by a flag, we could target the data attributes in our CSS…

:root { —primary-color: var(—primary-green-safe); }

:root[data-theme=’osmo’] { —primary-color: var(—color-blue-500); }

Slide 127

Slide 127

…And change values by adjusting the data attribute

We used this 1:1 mapping of our values and tested our rebrand by toggling on our flag.

This was especially helpful because we could introduce a new set of semantic tokens quickly without much fuss.

:root { —primary-color: var(—primary-green-safe); }

:root[data-theme=’osmo’] { —primary-color: var(—color-blue-500); }

Slide 128

Slide 128

Now, we could have a separate set of base tokens…

:root { —color-error-dark: var(—danger-red-dark); —color-error-base: var(—danger-red-base); —color-info-base: var(—info-blue-base); —color-warning-base: var(—alert-orange-base); —color-success-base: var(—primary-green-base);

—focus-color: var(—info-blue-safe); —primary-color: var(—primary-green-safe); —text-color: var(—gray-black); —text-color-danger: var(—danger-red-dark); }

Slide 129

Slide 129

Then we could create a new set of application-level tokens…

:root { —color-error-dark: var(—danger-red-dark); —color-error-base: var(—danger-red-base); —color-info-base: var(—info-blue-base); —color-warning-base: var(—alert-orange-base); —color-success-base: var(—primary-green-base);

—focus-color: var(—info-blue-safe); —primary-color: var(—primary-green-safe); —text-color: var(—gray-black); —text-color-danger: var(—danger-red-dark); }

Slide 130

Slide 130

And change values by adjusting the data attribute

By leveraging those data attributes through feature flagging, we could test out our new token values in the app at runtime, without impacting any of the other teams’ work.

:root[data-theme=’osmo’]

:root[data-theme=’osmo’] { —color-error-dark: var(—color-system-red-600); —color-error-base: var(—color-system-red-400) —color-info-base: var(—color-blue-400); —color-warning-base: var(—color-system-yellow-500); —color-success-base: var(—color-system-green-600);

—focus-color: var(—color-blue-500); —primary-color: var(—color-blue-500); —text-color: var(—color-black-300); —text-color-danger: var(—color-system-red-600); }

Slide 131

Slide 131

Including re-mapping tokens for our font family, test, adjust values quickly

:root[data-inter=’true’]

:root[data-theme=’osmo’]

Slide 132

Slide 132

And set up some component tokens

:root { —Button-color-background-primary: var(—color-success-safe); —Button-color-background-primary-hover: var(—color-success-dark); —Button-color-background-primary-focus: var(—color-success-dark); —Button-color-background-primary-active: var(—color-success-dark);

—Button-color-border-primary: var(—color-success-dark); —Button-color-border-primary-hover: var(—color-success-dark); —Button-color-border-primary-focus: var(—color-success-dark); —Button-color-border-primary-active: var(—color-success-dark);

—Button-color-text-primary: var(—white); }

Slide 133

Slide 133

To theme and streamline our CSS.

Even better, we could use this data attribute to wrap blocks of CSS code, and when it’s time to remove legacy styles, we can quickly remove blocks of code later on.

:root[data-theme=’osmo’] { —Button-color-background-primary: var(—primary-color); —Button-color-background-primary-hover: var(—color-blue-600); —Button-color-background-primary-focus: var(—color-blue-700); —Button-color-background-primary-active: var(—color-blue-700);

—Button-color-border-primary: var(—primary-color); —Button-color-border-primary-hover: var(—color-blue-600); —Button-color-border-primary-focus: var(—color-blue-700); —Button-color-border-primary-active: var(—color-blue-700); —Button-color-text-primary: var(—white); }

Slide 134

Slide 134

Performance optimizations

We also used flags in other ways for performance optimizations

Slide 135

Slide 135

In our HTML templates…

Slide 136

Slide 136

We used the LaunchDarkly Go SDK and Go templates on the backend to bootstrap our flag data into our HTML templates.

This would give us just enough information to render our pages.

Slide 137

Slide 137

And used the enable-2021-rebrand feature flag to set the data-theme attribute as a performance optimization.

This way, we could have the HTML templates check for this flag and render the correct theme while we wait for the JavaScript to load and initialize the HTML, to avoid a flash of incorrectly-styled content

{{% if ._flags.Enable2021Rebrand %}}

<html lang=”en” data-theme=”osmo”> {{% else %}} <html lang=”en” data-theme=”legacy”> {{% end %}}

Slide 138

Slide 138

We also used the enable-inter flag value preload this variable font only if the flag was on.

This way, our app performance wouldn’t take a hit from loading any unnecessary fonts if the flag was off.

{{% if ._flags.Enable2021Rebrand %}}

<html lang=”en” data-theme=”osmo”> {{% else %}} <html lang=”en” data-theme=”legacy”> {{% end %}}

Slide 139

Slide 139

For UI updates…

Slide 140

Slide 140

Left nav

We used feature flags to control whether we would show/hide content in our left nav

Slide 141

Slide 141

For example, if our rebrand flag was off

import { forwardRef, Ref } from ‘react’; import { useTheme } from ‘theme’;

export function AppNavItem({ className, name, icon, href, itemRef, …other }: AppNavItemProps) {

const { theme } = useTheme();

const navItem = React.createElement( ‘a’, { className: classes, href, …other, ref: itemRef }, [ theme === ‘legacy’ && icon && React.cloneElement(icon, { key: ‘icon’, className: ‘AppNav-itemIcon’, ‘aria-hidden’: true }), <span className=”AppNav-itemText” key=”name”> <span className=”AppNav-itemName”>{name}</span> </span> ], );

return navItem; }

export default forwardRef<Element, AppNavItemProps>((props, ref) => <AppNavItem {…props} itemRef={ref} />);

Slide 142

Slide 142

…the legacy theme would be true...

const { theme } = useTheme();

const navItem = React.createElement( ‘a’, { className: classes, href, …other, ref: itemRef }, [ theme === ‘legacy’ && icon && React.cloneElement(icon, { key: ‘icon’, className: ‘AppNav-itemIcon’, ‘aria-hidden’: true }), <span className=”AppNav-itemText” key=”name”> <span className=”AppNav-itemName”>{name}</span> </span> ], );

return navItem; }

Slide 143

Slide 143

…and we’d show icons in the left nav

const { theme } = useTheme();

const navItem = React.createElement( ‘a’, { className: classes, href, …other, ref: itemRef }, [ theme === ‘legacy’ && icon && React.cloneElement(icon, { key: ‘icon’, className: ‘AppNav-itemIcon’, ‘aria-hidden’: true }), <span className=”AppNav-itemText” key=”name”> <span className=”AppNav-itemName”>{name}</span> </span> ], );

return navItem;

Slide 144

Slide 144

image: LaunchDarkly left nav in legacy view

So users would have the expected legacy UI experience

Slide 145

Slide 145

image: LaunchDarkly left nav in rebranded view

While we worked on the rebranded nav in secret

Slide 146

Slide 146

Top nav

Slide 147

Slide 147

image: LaunchDarkly legacy view without top nav

There wasn’t a top nav in the legacy UI…

Slide 148

Slide 148

image: LaunchDarkly rebranded view with top nav

…and now there is one.

Slide 149

Slide 149

image: TopBar Spike feature flag targeting view

By adding the topbar-spike flag with the rebrand prerequisite

Slide 150

Slide 150

We could add the top nav into the rebranded UI by turning on the topbar-spike flag.

import classNames from ‘classnames’; import { useTheme } from ‘theme’; import Topbar from ‘components/Topbar/Topbar’; import ‘stylesheets/components/App’;

export const App = ({ className, children, isTopbarSpikeEnabled, }: AppProps) => { const classes = classNames(‘App’, className, { ‘App—with-top-bar’: isTopbarSpikeEnabled, }); const { theme } = useTheme();

return ( <div className={classes}> <SkipToContent /> {isTopbarSpikeEnabled && <Topbar />} <AppNavContainer theme={theme} isTopbarSpikeEnabled={isTopbarSpikeEnabled} /> <main className={classNames(‘App-main’, { ‘App-main-with-topbar’: isTopbarSpikeEnabled })} id=”content” > {children} </main> </div> ); };

export default App;

Slide 151

Slide 151

Since this flag was dependent on the enable-2021-rebrand flag...

const classes = classNames(‘App’, className, { ‘App—with-top-bar’: isTopbarSpikeEnabled, });

return ( <div className={classes}> <SkipToContent /> {isTopbarSpikeEnabled && <Topbar />} <AppNavContainer isTopbarSpikeEnabled={isTopbarSpikeEnabled} /> <main id=”content”> {children} </main> </div> ); };

Slide 152

Slide 152

...we could test this in isolation

const classes = classNames(‘App’, className, { ‘App—with-top-bar’: isTopbarSpikeEnabled, });

return ( <div className={classes}> <SkipToContent /> {isTopbarSpikeEnabled && <Topbar />} <AppNavContainer isTopbarSpikeEnabled={isTopbarSpikeEnabled} /> <main id=”content”> {children} </main> </div> ); };

Slide 153

Slide 153

image: LaunchDarkly Switch environment dropdown view

With LaunchDarkly, whenever you create a new project you get two default environments, production, and test, and they are set to specific colors by default.

Slide 154

Slide 154

image: Rebrand Env Colors flag targeting view

We wanted to test out new default environment colors, so created the rebrand-env-colors multivariate flag…

Slide 155

Slide 155

image: Rebrand Env Colors flag variations view

…used blocks on JSON in our variations to test out color combinations on the fly…

Slide 156

Slide 156

And used Go to store the values.

if post.Environments == nil { colors := accounts.EnvironmentColors{} if err := json.Unmarshal(ctx.Flags.EnableRebrandEnvColors(), &colors); err != nil { return err } envs = accounts.MakeDefaultEnvironments(“”, colors)

Slide 157

Slide 157

Favicons

Lastly… Favicons. It’s always a nice touch to have your favicon ready to go when you roll out your rebrand rather than scrambling to update at the last moment.

Especially if you are using them in more than one environment.

Slide 158

Slide 158

image: Favicon Env variations flag targeting view

By adding the favicons-env-variations multivariate flag…

Slide 159

Slide 159

image: Favicon Env variations flag variations view

We could use strings in our variations…

Slide 160

Slide 160

And serve up whichever favicon version we needed, depending on the environment, in our HTML template.

{{% if ._flags.Enable2021Rebrand %}} <link rel=”icon” type=”image/svg+xml” href=”{{% ./img/{{% ._flags.FaviconEnvVariations %}}.svg”> <link rel=”icon” type=”image/png” href=”{{% ./img/{{% ._flags.FaviconEnvVariations %}}.png”> {{% else %}} <link rel=”icon” type=”image/png” href=”{{% ./img/{{% ._favicon %}}” /> <link rel=”mask-icon” href=”{{% ./img/mask-icon.svg” color=”#055177” /> {{% end %}}

Slide 161

Slide 161

Phew!

That was a lot… So to wrap up…

Slide 162

Slide 162

image: The Ruling Ring of Isildur's Bane. One flag… …to rule them all.

With the power of feature flags

Slide 163

Slide 163

In a 10-week time frame.

Slide 164

Slide 164

We implemented a substantial amount of changes to a product app.

Slide 165

Slide 165

image: ree baby raccoons nestled inside a tree trunk... with a calico cat disguising themself as a sibling

And rolled out a product rebrand in stealth mode.

So I figure… if we could do it, so can you.

Slide 166

Slide 166

Thank you.

Slide 167

Slide 167

@resource11 Twitter | Instagram | GitHub