Surface

The box every effect of Axiom draws on: it owns the shape, the tilt and the frame scheduler.

A surface is the box that every visual effect of Axiom Design System draws on. Wrap your element with ax-surface, pick the layers you need in the layers attribute, and shape it with custom properties: corners, edge features, border and tilt. Your content is always on top of the layers.

The Playground (/playground) has every property and many ready-made shapes. Underneath, Shape computes the outline and Typed OM reads the custom properties. This page shows the properties one idea at a time.

Shape properties are not inherited, so set them on the ax-surface element itself. Surfaces only work while they are visible and read their styles only after something changed.

Installation

shell
npm install @ax-design/surface

and import it:

js
import { register } from '@ax-design/surface';
register();

If you don't use a bundler, load the script instead:

html
<script src="https://unpkg.com/@ax-design/surface/build/main.js"></script>
<script>AxSurface.register();</script>
Corners

Corners

The radius sets every corner. Percentages are relative to the shorter side, and like border-radius, radii that do not fit are scaled down.

The corner shape has the same keywords as the CSS corner-shape property: round, bevel, scoop and notch. Bevel, scoop and notch corners meet the sides in sharp points; the shoulder radius rounds those points.

Try it

30 %
0 px
html
<ax-surface class="shape" layers="fill">...</ax-surface>
css
.shape {
  --ax-radius: 30%;
  --ax-corner-shape: scoop;
  --ax-corner-shoulder-radius: 0px;
}
Parallelogram

Moved corners

Every corner can have its own radius, shape and shoulder radius, for example --ax-top-left-radius and --ax-corner-top-left-shape. A corner that sets nothing uses the general value.

A corner can also move into the shape. Positive shifts move the corner toward the middle of the shape: shift the top left and the bottom right corners on x and you get a trapezoid or a parallelogram, depending on the two values. Percentages are relative to the width for x and to the height for y. Negative values move the corner out of the box.

Try it

20 %
20 %
0 px
css
.shape {
  --ax-corner-top-left-shift-x: 20%;
  --ax-corner-bottom-right-shift-x: 20%;
  --ax-top-right-radius: 0px;
}
Top edge

Edge features

Each side can carry a feature: a bump or a notch. The kind is arc (circular), rect, triangle, trapezoid or cusp. Cusp is two arcs that meet in a point: pointing in it pinches the side, pointing out it makes a pointed arch.

Depth is how far the feature goes: positive values go out of the shape, negative values go into it. Span is its width along the side. At is where its middle sits; sides run clockwise, so the top goes from left to right. This example uses the top side.

Try it

-20 px
40 %
css
.shape {
  --ax-edge-top-kind: arc;
  --ax-edge-top-depth: -20px;
  --ax-edge-top-span: 40%;
  --ax-edge-top-at: 50%;
}
Cookie

All sides at once

The --ax-edge-* properties without a side set the feature of all four sides. A side that sets its own value, like --ax-edge-top-depth, wins over them.

Sides run clockwise, so the same --ax-edge-at on every side turns each feature the same way around the shape. The result has rotational symmetry, not mirror symmetry. The tip radius rounds the point of a cusp.

Try it

-8 px
20 px
css
.shape {
  --ax-radius: 30%;
  --ax-edge-kind: cusp;
  --ax-edge-depth: -8px;
  --ax-edge-span: 100%;
  --ax-edge-tip-radius: 20px;
  /* A side can still go its own way. */
  --ax-edge-top-depth: -12px;
}
Press me

Border, fill and tilt

The fill layer paints the inside of the shape with --ax-fill-color and the border with --ax-border-color. The border is as wide on every side as --ax-border-width says, or per side with --ax-border-top-width and its siblings.

Set --ax-tilt-mode and the surface tilts toward the point you press. Absolute sinks the pressed side by the tilt depth, relative tilts it by a number of degrees. The border keeps the same width on screen while the surface tilts. Press the shape to try it.

Try it

#FFFFFF
14%
2 px
css
.shape {
  --ax-fill-color: rgba(255, 255, 255, 0.14);
  --ax-border-color: white;
  --ax-border-width: 2px;
  --ax-tilt-mode: absolute;
  --ax-tilt-depth: 3px;   /* absolute */
  --ax-tilt-factor: 8;    /* relative, in degrees */
}
Layers

Layers together

The layers attribute lists what the surface draws, in order. Your content is on top of all of them. Fill comes with Surface, the others come from their packages: elevation (a shadow that follows the shape), reveal and acrylic.

Put elevation first, so the other layers paint on top of it. Pick a list below. The background of this preview is an image, so the acrylic has something to blur.

Try it

16
js
import { register } from '@ax-design/surface';
import { registerAcrylicLayer } from '@ax-design/acrylic';
import { registerElevationLayer } from '@ax-design/elevation';
import { registerRevealLayer } from '@ax-design/reveal-highlight';

registerAcrylicLayer();
registerElevationLayer();
registerRevealLayer();
register();
html
<ax-surface class="shape" layers="acrylic reveal">...</ax-surface>
css
.shape {
  --elevation-depth: 16;
}

Clip

Add the clip attribute and the content of the surface is cut to its shape, so an image or a video fills a tile of any shape. The cut follows the shape when it changes, and tilts with the content when the surface is pressed.

Without clip the content keeps its own box, and the shape only draws around it.

Try it

24 %
html
<ax-surface class="tile" clip layers="reveal">
  <img src="cover.jpg" alt="" style="display: block; width: 100%; height: 100%; object-fit: cover">
</ax-surface>
css
.tile {
  width: 220px;
  height: 220px;
  --ax-radius: 24%;
  --ax-corner-shape: scoop;
}

Reference

layers attribute

space separated names

The layers the surface draws, in order: fill, reveal, acrylic, elevation or one you registered.

clip attribute

boolean, default off

Cuts the content to the shape. The cut follows the shape and the tilt.

observe attribute

precise

Use it when a style of yours depends on something a surface cannot see, such as an element inside another component's shadow root.

--ax-radius property

<length-percentage>, default 0px

The radius of every corner. Percentages are relative to the shorter side.

--ax-{corner}-radius property

<length-percentage>, default -1px

The radius of one corner (top-left, top-right, bottom-right, bottom-left). A negative value means use --ax-radius.

--ax-corner-shape property

round | bevel | scoop | notch, default round

The shape of every corner.

--ax-corner-{corner}-shape property

round | bevel | scoop | notch | auto, default auto

The shape of one corner. auto means use --ax-corner-shape.

--ax-corner-shoulder-radius property

<length>, default 0px

Rounds the two points where a bevel, scoop or notch corner meets the sides. Round corners ignore it.

--ax-corner-{corner}-shoulder-radius property

<length>, default 0px

The shoulder radius of one corner. Wins over the general property.

--ax-corner-{corner}-shift-x property

<length-percentage>, default 0px

Moves one corner point along x. Percentages are relative to the width. Negative values move it out of the box.

--ax-corner-{corner}-shift-y property

<length-percentage>, default 0px

Moves one corner point along y. Percentages are relative to the height.

--ax-edge-{side}-kind property

flat | arc | rect | triangle | trapezoid | cusp, default flat

The feature on one side (top, right, bottom, left). Arc is a circular bump or notch, cusp is two arcs that meet in a point.

--ax-edge-{side}-depth property

<length>, default 0px

How far the feature goes. Positive values go out of the shape, negative values go into it.

--ax-edge-{side}-span property

<length-percentage>, default 33%

The width of the feature along the side. Percentages are relative to the length of the side.

--ax-edge-{side}-at property

<length-percentage>, default 50%

Where the middle of the feature sits. Sides run clockwise: top left to right, right top to bottom, bottom right to left, left bottom to top.

--ax-edge-{side}-ratio property

<number>, default 0.5

For trapezoid only: the width of the far end compared to the base. 1 gives a rectangle, 0 a triangle.

--ax-edge-{side}-tip-radius property

<length>, default 0px

Rounds the far corners of the feature, or the point of a cusp.

--ax-edge-{side}-shoulder-radius property

<length>, default 0px

Rounds the corners where the feature meets the side.

--ax-edge-kind, --ax-edge-depth, --ax-edge-span, --ax-edge-at, --ax-edge-ratio, --ax-edge-tip-radius, --ax-edge-shoulder-radius property

as the side properties

The feature of all four sides at once. Each is the default of the side property with the same name; a side that sets its own value wins.

--ax-border-width property

<length>, default 1px

The width of the border on every side. It stays equally thick on screen while the surface tilts.

--ax-border-{side}-width property

<length>, default -1px

The width of the border on one side. A negative value means use --ax-border-width.

--ax-tilt-mode property

off | absolute | relative, default off

Tilts the surface toward the point you press. Inherited.

--ax-tilt-depth property

<length>, default 1px

For absolute mode: how far the pressed side sinks.

--ax-tilt-factor property

<number>, default 2

For relative mode: the angle in degrees.

--ax-tilt-zoom property

<length>, default 0.8px

How far the whole surface falls when you press its center.

--ax-tilt-perspective property

<length>, default 25px

The perspective of the tilt.

--ax-tilt-direction property

normal | inverted, default normal

Inverted lifts the pressed side instead of sinking it.

--ax-fill-color property

<color>, default transparent

For the fill layer: the color inside the shape.

--ax-border-color property

<color>, default transparent

For the fill layer: the color of the border.

refresh() method

() => void

Reads the styles of this surface again. Use it after a change the surface cannot notice.

refreshAll() method

() => void

Reads the styles of every surface again. Exported by the package.

release() method

() => void

Ends a press before the pointer is up, for containers that take the gesture over (a tile drag). Since 0.2.1.

registerLayer(name, create) method

(name: string, create: () => Layer) => void

Registers a layer you wrote, to use in the layers attribute.