One Button, many contracts: component tokens meet interaction behavior

Ant Design's Button is a useful test of the customization model because it looks simple while carrying several kinds of responsibility. This chapter follows one Button from local props and ConfigProvider defaults through color and variant resolution, Component Tokens, semantic styling, and rendered interaction states. The practical goal is to tell a global theme change from a Button-specific style decision and from a behavior change that must be expressed explicitly.

From props and context to a stable style vocabulary

A Button has to answer more questions than “what color is it?” It may be primary, default, dashed, text, or link. It may be dangerous, loading, disabled, compact, large, round, circular, full width, or rendered as an anchor. These options overlap, and older applications may still use the type prop while newer code uses color and variant. A stable component contract must turn that variety into a predictable set of states.

Ant Design does this by treating the older type values as shorthand for color-and-variant pairs. The default type maps to a default color with an outlined variant; primary maps to the primary color with a solid variant; dashed maps to a default color with a dashed variant; link and text map to their corresponding unbordered or text-oriented choices. If a caller supplies both modern color and variant values, those explicit values take priority. The compatibility layer therefore does more than preserve old syntax. It translates several public vocabularies into one internal style vocabulary that the rest of the Button can use.

The resolution does not happen in isolation. The Button reads the current component configuration from ConfigProvider, including provider defaults for color, variant, shape, loading icon, class names, and styles. It also reads direction, size, and disabled context. Local props are more specific than those defaults, so a Button can opt out of a subtree's normal variant or disabled state when the component contract permits it. This is the same scope principle seen earlier, now applied at the point where a real control is rendered.

The Button then turns the resolved values into a set of semantic classes and styles. Its prefix comes from the provider rather than being assumed. Its classes identify the shape, color, variant, size, loading state, block behavior, direction, and icon placement. Those classes do not themselves contain every visual decision. They are the handles through which the registered style hook connects the component's state to the resolved token values.

Precedence is what keeps this flexibility from becoming ambiguity. A local color and variant can override a provider default, while a legacy type value remains a compatibility shorthand when more explicit values are absent. A local disabled or size choice is resolved against the corresponding context rather than merged into an arbitrary style string. This gives the component a stable answer when several sources speak at once: the most specific supported intent wins, and the style layer receives one resolved state rather than a pile of competing declarations.

The same resolution protects compatibility. Older product code does not need to understand the newer color-and-variant vocabulary to receive the modern style path, while newer code can express the relationship directly. Compatibility is therefore not a separate visual branch that must be maintained forever; it is a translation step before the shared component contract runs. That is a small but important systems choice: variation is absorbed at the boundary, not allowed to multiply inside every state rule.