Docs
Surface Inversion

Surface Inversion

Surface lets a container declare what kind of surface it's on (positive or negative) and, with that, makes children resolve Foundation aliases automatically inverted — without any child knowing what context it's in.

The problem

Without Surface, a button on a dark background must explicitly swap its text color / border / hover. Every use becomes duplicated code prone to visual drift.

The primitive

Containers that accept surface:

  • <Flex surface="negative">
  • <Base surface="negative">
  • (any container inheriting from that base — Card, Paper, etc.)

surface="positive" is the implicit default (no need to declare it).

import { Flex } from '@apollion-dsi/core/containers/flex';
import { Button } from '@apollion-dsi/core/elements/button';
import { Text } from '@apollion-dsi/core/elements/text';
 
// light background, "normal" text/button
<Flex surface="positive" p="large" bgColor="baseLight">
  <Text>Light header</Text>
  <Button text="OK" />
</Flex>
 
// dark background, text/button automatically inverted
<Flex surface="negative" p="large" bgColor="baseDark">
  <Text>Dark header</Text>
  <Button text="OK" />
</Flex>

The children don't change. The ThemeProvider resolves the inversion.

Live demo

The same <Text> and <Button> rendered in positive (default, light background) and negative (dark background) containers, with no explicit color prop on the children. Note the automatic contrast.

surface="positive"Text resolves to the current mode's normal palette.
surface="negative"Same Text — now inverted by the provider.

When to use it

  • Yes: Hero sections, dark footers, modals with inverted chrome, toolbars over images.
  • No: For individual components. Surface is a container property, not an element one. If you need to invert a single button, use Foundation/Semantic with a variant.

Surface respects the current mode

Surface inversion respects the theme's active light/dark mode: the negative surface carries the opposite polarity's surface ladder — in light mode surface="negative" gets the dark ladder (dark paper, light ink), in dark mode the light one — derived by the same engine from the same seeds, never a hand-swapped palette. Structural tokens (theme.colors.*) stay untouched. See Dark Mode Engine.

See also