The Theme Toggle Is a Tiny Internet

Warish

Hatched by Warish

Aug 08, 2026

12 min read

93%

0

What does a dark mode button have in common with the infrastructure that delivers a web page from a server thousands of miles away?

At first, almost nothing. One is a small interface detail controlled by a click. The other involves routers, IP addresses, DNS servers, and protocols that move packets across the planet. Yet both work for the same underlying reason: they separate what something is from where it is, how it moves, and what state it is currently in.

That separation is one of the quiet master ideas of computing. It explains why we can type google.com instead of memorizing an IP address. It explains why a website can switch its entire appearance by changing one class on the body element. It explains why a preference can survive a page refresh. More broadly, it offers a design principle for building systems that remain understandable when they grow complicated.

The deepest connection between the internet and a theme toggle is not technical similarity. It is architectural similarity. Both turn complexity into a small number of stable agreements.

The hidden problem behind every simple interaction

A button labeled “dark mode” appears to perform one action: click it, and the screen changes. But that visible behavior conceals several separate problems.

First, the interface needs a representation of the current mode. Is the page in light mode or dark mode? Second, it needs a mechanism for changing state when the user clicks. Third, the visual system needs to know how that state affects the page. Finally, the system must decide whether the choice should disappear when the page closes or remain available the next time the user returns.

A naïve implementation might directly change the background color, text color, button color, border color, and every other visual property one by one. That works for a tiny demonstration. It becomes fragile as soon as the page has dozens of components. Every new component must remember the rules for both themes, and every future change requires hunting through scattered declarations.

A more durable design introduces a layer of indirection. Instead of assigning raw colors everywhere, the stylesheet assigns variables such as a background variable and a text variable. The page uses those variables consistently. A dark mode class then changes the values of the variables in one place.

The button does not need to understand every visual decision on the page. It only changes a state marker. The CSS system interprets that marker. This is a small but profound division of labor:

  1. The button detects an event.
  2. JavaScript changes a state.
  3. CSS interprets the state.
  4. The browser renders the result.

The user experiences one seamless action. Underneath, several specialized layers cooperate through a simple contract.

The internet works in much the same way. A browser does not need to know the physical location of every server. DNS translates a human readable domain name into an IP address. Routers move packets toward the appropriate destination. TCP helps ensure that the packets arrive and can be reconstructed into a reliable exchange. Each layer performs a narrower task so that no single part must understand the entire system.

Robust systems do not eliminate complexity. They place complexity behind stable interfaces.

That is why the connection matters. A theme toggle is not merely a beginner exercise in JavaScript. It is a miniature lesson in network architecture.

Names are not locations, and state is not appearance

One of the internet’s most important conveniences is the difference between a name and an address. People use names because names are meaningful and relatively stable. Machines use addresses because addresses tell them where to send data.

google.com is not the same thing as the numerical address returned by DNS. The name expresses identity from the user’s perspective. The IP address expresses a current location in the network. Separating the two allows the underlying infrastructure to change without forcing people to learn a new vocabulary.

The same distinction appears in interface design. The phrase “dark mode” should represent a state or intention, not a list of visual instructions. It should not mean “set this background to black, that text to white, this border to gray, and move this icon.” It should mean that the page is operating under a different visual theme. CSS variables then translate that theme into concrete values.

This is a form of naming that protects the rest of the system from unnecessary detail.

Consider two approaches:

.card {
  background: #ffffff;
  color: #222222;
}

This code binds a component directly to specific values. It is like embedding an IP address wherever a person wants to visit a website. It may work, but it makes change expensive.

A more flexible approach is:

:root {
  --background: #ffffff;
  --text: #222222;
}

.dark-mode {
  --background: #111111;
  --text: #eeeeee;
}

.card {
  background: var(--background);
  color: var(--text);
}

Now the component depends on a semantic contract rather than a fixed location. The card asks for the background and text appropriate to the current theme. It does not need to know whether those values are white and dark gray, or nearly black and nearly white.

This distinction between identity, location, and appearance is useful far beyond themes. A user account has an identity, but it may be stored in different databases. A document has a name, but its files may move between servers. A service has an interface, but its internal implementation may be replaced entirely.

In each case, the system becomes easier to evolve when consumers rely on stable meaning rather than unstable details.

There is also a second distinction: state is not appearance. The dark screen is an output. The dark mode class is a representation of state. Local storage is a persistence mechanism. The click event is an input. Confusing these layers creates brittle code.

If the button directly manipulates every visible element, the interface has no clear source of truth. If the page stores a color value but the application really cares about a theme, the stored data is too close to presentation. A better system stores the smallest meaningful fact, such as theme = dark, and lets the rendering layer decide what that fact looks like.

The internet’s packet system follows a similar discipline. A packet carries information through a protocol, but the application does not need to manage every physical movement through every router. It relies on abstractions that preserve the intended exchange while hiding the route.

Persistence turns a moment into a relationship

A click is temporary. A preference is durable.

This difference is easy to overlook because the first version of a theme toggle feels complete. Add a click handler, toggle the dark mode class on the body, and the page changes immediately. But reload the page, and the interface returns to light mode. The system responded to an event, but it did not remember the user.

That failure reveals two different kinds of state:

  • Transient state, which exists during the current page session.
  • Persistent state, which survives the destruction and recreation of the page.

The class on the body is transient. When the browser rebuilds the document, that class disappears unless code adds it again. Local storage provides a small persistent memory. When the user selects dark mode, the application stores a key and value. On page load, it checks that value and reconstructs the appropriate current state.

The sequence is simple:

  1. The user clicks the toggle.
  2. The application changes the theme state.
  3. The application records the state in local storage.
  4. The page is reloaded.
  5. The application reads the stored preference.
  6. The page restores the theme before the user must choose again.

This is more than convenience. It changes the psychological meaning of the interface. Without persistence, the button controls a moment. With persistence, the application acknowledges an ongoing relationship with the user.

The internet depends on this same distinction between immediate activity and durable configuration. A local device receives a private address within a local network. The router has another address as it connects to an internet provider. These addresses describe different scopes and different lifetimes. A device’s local address may be assigned for a session, while a domain name remains a durable human facing reference.

The key lesson is that state only makes sense when its scope and lifetime are explicit. A local IP address is not a universal identity. A CSS class is not permanent memory. A value in local storage is not automatically synchronized across devices. Each belongs to a particular layer and context.

Many software bugs are really scope mistakes disguised as logic mistakes. A preference is stored in memory when it should persist. A global variable is used for a local concern. A component assumes that a state marker exists when it has actually been removed during a render. A device treats a private network address as though it were publicly reachable.

Asking “where does this state live, and how long should it live?” is often more valuable than asking “what line of code changes it?”

Protocols are agreements that let strangers cooperate

The internet is not a single machine. It is an enormous collection of independent systems: devices, routers, servers, providers, and networks. These systems can cooperate because they follow shared protocols. A protocol is not merely a technical format. It is an agreement about how to begin, continue, interpret, and complete an exchange.

A browser and server do not need to be built by the same company. They do not need to share internal code. They only need to honor compatible rules for communicating. TCP, for example, handles the reliable delivery of packets between endpoints. It gives the participants a way to deal with the fact that data may travel in separate pieces through an imperfect network.

A well designed interface also depends on protocols, even if nobody calls them that. The button has a contract: when clicked, it triggers a handler. The handler has a contract: it changes the theme state. CSS has a contract: the presence of a state class selects a different set of variable values. Local storage has a contract: a known key can be written and later retrieved.

These agreements make the parts replaceable. The button could be redesigned as a switch. The storage mechanism could later become a server side preference. The CSS could support three themes instead of two. If each layer communicates through a clear state model, these changes do not require rewriting the entire application.

This suggests a practical test for architecture:

If replacing one implementation forces you to rewrite unrelated parts, your boundaries are probably too weak.

Suppose the theme toggle stores the literal value #111111 in local storage. That decision couples persistence to presentation. If the design team later chooses a softer dark palette, old preferences become misleading or require migration. If the application stores the semantic value dark, the visual system can evolve independently.

Likewise, if every component listens to the button directly, the button has become a hidden central controller. A clearer design lets components respond to a shared theme state. This resembles a network where applications communicate through protocols rather than reaching into the private internals of routers.

The value of abstraction is not that it makes systems simpler in an absolute sense. It makes change local. DNS can update the address behind a name. CSS can update colors behind a theme variable. Local storage can preserve a preference while the page is reconstructed. Protocols keep the rest of the system from needing to know why those changes occurred.

The design principle: preserve intent, vary implementation

The most useful synthesis is a rule for building almost anything digital: store intent at the highest stable level, and let lower layers decide how to realize it.

For a theme, the intent is “the user prefers dark mode.” The implementation may involve a body class, CSS variables, media queries, an icon change, contrast adjustments, and a stored preference. These details can change without changing the intent.

For a web destination, the intent is “connect me to this named service.” DNS and routing determine the current path. The user should not have to care which numerical address or physical route is used.

For reliable communication, the intent is “deliver this data correctly.” TCP manages packets and acknowledgments so the application can operate at a higher level.

This principle also gives us a way to diagnose bad design. Ask three questions:

  1. What is the stable intention? For example, a selected theme or a requested service.
  2. What is the current representation of that intention? For example, a class, a variable, a domain name, or an IP address.
  3. Which layer is responsible for translating it into action? For example, CSS, JavaScript, DNS, routing, or TCP.

When these questions have clear answers, a system is usually easier to debug. If they do not, different concerns have probably been fused together.

A useful implementation pattern follows naturally:

  • Keep state semantic. Store dark, not a raw color.
  • Keep presentation centralized. Let variables translate state into visual values.
  • Keep events narrow. A click should announce a change, not manually repaint the entire page.
  • Keep persistence deliberate. Save only what should survive a reload.
  • Keep names stable. Use meaningful identifiers instead of exposing volatile implementation details.
  • Keep protocols explicit. Define how one layer informs the next.

These practices apply to a settings panel, a mobile application, an API, or a distributed service. They are not about writing more code. They are about ensuring that each piece of code has one intelligible responsibility.

Key Takeaways

  • Separate meaning from implementation. Represent a user preference as a semantic state such as dark, rather than storing the visual details that happen to express it today.
  • Use indirection to make change local. CSS variables, DNS names, and network protocols all allow underlying details to change without disturbing every consumer.
  • Distinguish transient from persistent state. A body class controls the current page; local storage preserves a preference beyond the page’s lifetime.
  • Define scope and lifetime explicitly. Ask where a value lives, who can access it, and when it should disappear.
  • Design around contracts. A button, stylesheet, storage layer, or network service becomes replaceable when its responsibilities and interfaces are clear.

The humble theme toggle teaches an unexpectedly large lesson. A reliable system does not ask every part to know everything. It gives each part a name, a boundary, and a protocol for cooperation.

That is why the internet can connect strangers, why a domain can outlive a server, and why one class on a document can recolor an entire interface. Beneath both the global network and the local page is the same act of engineering: preserve the user’s intention while allowing the machinery beneath it to change.

The next time a button changes a screen, do not see only a visual effect. See a tiny network. An event travels to a handler. State is represented, interpreted, and perhaps remembered. Several layers cooperate without exposing all their complexity.

The best interfaces are not the ones with the most visible control. They are the ones where a small, meaningful signal can travel through a well designed system and produce exactly the result the user intended.

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣