Surface

A container with a background, an inset and corners, but no layout of its own.

BetaSince 1.0.0
On this page

Import

import {
  isPlaceholder,
  placeholders,
  Surface,
  useSkeleton,
} from '@envsemble/envui';

Usage

A Surface gives its content a background, an inset and corners.

  • Lay out what it holds through render, with a Column or a Row, so the surface and its layout are one element.
  • render also gives it its meaning, such as render={<section />}, and ref reaches the rendered element.
DetailPane.tsx
import { useId } from 'react';
import { Column, Surface, Text } from '@envsemble/envui';

export default function DetailPane() {
  const heading = useId();
  return (
    <Surface padding={5} render={<Column render={<section />} gap={3} aria-labelledby={heading} />}>
      <Text render={<h3 />} variant="title" id={heading}>
        Northwind launch
      </Text>
      <Text>Shared plans, notes and decisions for the spring launch.</Text>
      <Text variant="body-sm">Updated by Priya Shah</Text>
    </Surface>
  );
}

Backgrounds, corners and inset

  • background is default, the default, muted, page, raised or recessed. In every theme the page and the recessed well lie below default, and muted and raised a step above it, with one fill; the recessed well is a shade deeper than the page in dark, and between the page and the panes in light, where it takes the panes' fill at high contrast.
  • radius is xl, 1.75 rem and the default, sm, 0.5 rem, full, fully rounded ends, or none, square.
  • padding sets the inset as a step of the spacing scale, from 1, 0.25 rem, to 9, 4 rem, and 0 removes it; without it, the inset grows from 1.25 rem in narrow windows to 1.5 rem from 50 rem wide.

A group of controls, as wide as its controls in a Column with align="start":

<Surface
  padding={1}
  radius="full"
  role="group"
  aria-label="Diagram view"
  render={<Row wrap align="center" gap={4} />}
>
  <IconButton variant="ghost" size="sm" icon={<IconZoomOut />} aria-label="Zoom out" />
  <IconButton variant="ghost" size="sm" icon={<IconZoomIn />} aria-label="Zoom in" />
  <Button variant="secondary" size="sm">
    Fit to screen
  </Button>
</Surface>

Loading

skeleton shows the surface and everything inside it as a skeleton until its content arrives: text becomes bars of its text style's length, and controls filled shapes.

  • Render the same components over stand-in items, items ?? placeholders<Project>(3), whose fields are undefined; isPlaceholder(item) tells a stand-in by its id, and narrows the type, so the other branch reads the item's own fields.
  • A component of the application's own reads useSkeleton() to give its skeleton a look of its own, such as a chart's.
// A detail pane that is a skeleton until its project arrives: the text it will hold is bars.
<Surface skeleton={!project} style={{ inlineSize: '22rem' }}>
  <Column gap={3}>
    <Text render={<h3 />} variant="title">
      {project?.name}
    </Text>
    <Text>{project?.brief}</Text>
  </Column>
</Surface>

Customize

  • To recolor an area, its controls included, set the semantic tokens on a nested Theme, as Customize and extend shows.
  • CSS may target env-surface, with data-background and data-radius for the values other than the defaults.

Accessibility

You provide

  • A heading for a surface that is a region, and its name, such as aria-labelledby on a section.
  • role="group" and an aria-label on a group of controls; a Toolbar for one the arrow keys move through.

envUI provides

  • A div with no role of its own, or the element in render, such as render={<section />}.

Props

Surface

A container with a background, an inset and corners, but no layout of its own: spacing between its children, alignment and behavior come from what it holds, such as a Column. Card and other containers build on it.

PropTypeDescription
backgroundDefault 'default'
'default''muted''page''raised''recessed'
The fill: default, the background of panes and side regions; muted, the background of rows; page, the page background; raised, a block that stands out on a pane; or recessed, a well on the page that what it holds rises out of: a shade deeper than it in dark, and lighter than it and the panes in light.
paddingDefault 1.25 rem in narrow windows, growing to 1.5 rem from 50 rem wide
0123456789
The inset on every side, as a step of the spacing scale, from 1 (0.25 rem) to 9 (4 rem), such as 5 for 1.5 rem; 0 removes it. Without it, the inset follows the window's width.
radiusDefault 'xl'
'xl''sm''full''none'
The corner radius: xl, the large 1.75 rem corners of panes; sm, 0.5 rem for small blocks; full, fully rounded ends for a toolbar or a bar of filters; or none, square, for a region that meets the edges of its container.
skeletonDefault false
boolean
Shows it and its content as a busy skeleton while that loads; turning it on or off renders the content anew.
render
ReactElement
The element to render the surface as, for its meaning, such as render={<section />}, or another component to render it onto without a wrapper, such as a Column that lays out its content: the surface's class, style and attributes merge into its props. It must forward native props and refs. A div by default.
ref
Ref<RenderedElement>
The rendered element, a div by default. Its type is the element type parameter, which a ref passed in sets: a ref to the element that render makes, such as useRef<HTMLElement>(null) with render={<section />}, type-checks, and `SurfaceProps['ref']` is a ref to the default element, so a wrapper can forward it to one.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

placeholders

Stand-in items for data that is loading, so the page renders its normal components over them while a surface around them is a skeleton: `items ?? placeholders<Project>(3)`. Each item has only an `id`, `envui-placeholder-0`, `envui-placeholder-1` and so on, for keys; its other fields are undefined, which the type says, and the components render missing text as bars of a default length for its text style.

placeholders<Item = object>(count: number): Placeholder<Item>[]

isPlaceholder

Whether an item is one of the stand-ins that `placeholders()` returns, by its id, so it holds after the items are serialized, such as from a server component to a client one. It narrows the type: `isPlaceholder(item) ? <Skeleton /> : item.name`, where item.name is the item's own.

isPlaceholder<Item>(item: Item | Placeholder<Item>): item is Placeholder<Item>

useSkeleton

Whether the component is inside a skeleton: a surface with `skeleton` around it. A component of the application's own reads it to give its skeleton a look of its own, such as a chart's.

useSkeleton(): boolean

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

env-surface
A surface: fill, padding and corners.
data-background
default · muted · page · raised · recessed
data-radius
full · none · sm · xl

Color properties

Each sets a color of one instance, through style or CSS; its hover, focus and press follow it. They are public and versioned.

--env-surface-container
The surface's fill, which its contents take as the color beneath them.
--env-surface-content
The text.
--env-surface-outline
An edge in this color.

Changelog

1.0.0

skeleton, with placeholders(), isPlaceholder() and useSkeleton(). tone is background, with the names of the background roles: pane is default, row muted, canvas page and alternate raised; corners is radius: surface is xl, standard sm and pill full; render replaces as. The backgrounds take the semantic background colors.

1.0.0-prerelease.6

raised is a step above default in dark too, with muted's fill, and recessed lies below default in light too, between the page and the panes.

1.0.0-prerelease.5

background="recessed", a well on the page a shade deeper than it, in --env-color-bg-recessed.