Symptom

Every site that constructs a UI widget switches on theme — and adding a new theme means hunting every switch and adding a new branch in lockstep. One missed branch silently produces a half-themed UI that ships looking 'mostly right' until a designer or QA notices. The team's and both compound across every switch site.

Goal

A factory interface that produces a coherent family of widgets, with concrete factories enforcing family-internal consistency. Client code receives a factory and asks for products; it never names the concrete class and cannot mix widgets across themes by accident, supporting between client logic and family selection.

Before the pattern

function renderToolbar(theme) {
let button;
let textField;
if (theme === 'light') {
button = new LightButton();
textField = new LightTextField();
} else if (theme === 'dark') {
button = new DarkButton();
textField = new DarkTextField();
} else {
throw new Error(`unknown theme: ${theme}`);
}
return [button, textField];
}

After the pattern

class LightWidgetFactory {
createButton() { return new LightButton(); }
createTextField() { return new LightTextField(); }
}
class DarkWidgetFactory {
createButton() { return new DarkButton(); }
createTextField() { return new DarkTextField(); }
}
function renderToolbar(factory) {
return [factory.createButton(), factory.createTextField()];
}
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 3. The book's running example is a UI toolkit with multiple look-and-feel families; this JavaScript adaptation keeps the family-of-products structure with two concrete factories (light/dark theme) producing matching button and text-field widgets.
Pressure

Theme dispatch is Shotgun Surgery at every construction site. The type system has no way to enforce that all widgets in one render path belong to the same theme — that lives in convention and reviewer attention, both of which erode under deadline pressure. The of any theme edit stretches into every render path.

Tradeoff

The factory interface and concrete subfactories grow in lockstep with the product set. Every new product method must be implemented across every concrete factory. Adding one product becomes a one-place-times-N change instead of a one-place change, and the team's per product addition multiplies with the number of factories.

Relief

Client code reads as `factory.createButton(); factory.createTextField()` regardless of theme. New theme = one new concrete factory; products in one render path are family-coherent by construction; the linter or the type system catches missing factory methods at compile time, not at user-visible runtime. The team's drops because new themes add one file rather than N call-site edits.

Trap

The factory interface accretes product methods over time and becomes a god interface. Sub-families with optional products (e.g., one theme has a DateRangePicker, another doesn't) force either NotImplemented throws or interface segregation — both costlier than the original switch and harder to undo. The cure adds when the interface stops describing one coherent family.