Shape Morph
Shape morph is the M3 Expressive behaviour where a control’s corner radius animates on press or selection — a round button squaring off as you push it. It is on by default.
Turn it off globally
Section titled “Turn it off globally”<html data-shape-morph="off">Shapes freeze at their resting radius. Nothing else changes: the control still presses, still ripples, still shows its state layer.
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div style="display:flex;gap:12px;align-items:center;margin-block-end:16px;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">on</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div>
<div data-shape-morph="off" style="display:flex;gap:12px;align-items:center;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">off</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div>import { MdButton } from '@awc-ui/react';
export function Demo() {
return (
<>
<div style={{ display: 'flex', gap: '12px', alignItems: 'center', marginBlockEnd: '16px' }}>
<span style={{ width: '5rem', opacity: '.7', fontSize: '.8rem' }}>on</span>
<MdButton variant="filled">Press and hold</MdButton>
<MdButton variant="tonal" toggle>Toggle</MdButton>
</div>
<div data-shape-morph="off" style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
<span style={{ width: '5rem', opacity: '.7', fontSize: '.8rem' }}>off</span>
<MdButton variant="filled">Press and hold</MdButton>
<MdButton variant="tonal" toggle>Toggle</MdButton>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<div style="display:flex;gap:12px;align-items:center;margin-block-end:16px;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">on</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div>
<div data-shape-morph="off" style="display:flex;gap:12px;align-items:center;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">off</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<div style="display:flex;gap:12px;align-items:center;margin-block-end:16px;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">on</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div>
<div data-shape-morph="off" style="display:flex;gap:12px;align-items:center;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">off</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<div style="display:flex;gap:12px;align-items:center;margin-block-end:16px;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">on</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div>
<div data-shape-morph="off" style="display:flex;gap:12px;align-items:center;">
<span style="width:5rem;opacity:.7;font-size:.8rem;">off</span>
<md-button variant="filled">Press and hold</md-button>
<md-button variant="tonal" toggle>Toggle</md-button>
</div>Three levels, and opting back in
Section titled “Three levels, and opting back in”Same model as ripple: one custom property,
--md-sys-shape-morph-enabled (1 on, 0 frozen), settable at any level and
reversible at any level.
<html data-shape-morph="off"> <section data-shape-morph="on"> <md-button variant="filled">Morphs</md-button> </section>
<md-button variant="filled" shape-morph="on">Also morphs</md-button></html>| Scope | Attribute | Where |
|---|---|---|
| Global | data-shape-morph="off" / "on" | <html> or any ancestor |
| Region | data-shape-morph="off" / "on" | Any wrapper element |
| Component | shape-morph="off" / "on" | The component host |
How the freeze works
Section titled “How the freeze works”The value is 0/1 rather than a keyword so CSS can use it as
arithmetic. Each morphed radius is blended back toward its resting value:
border-radius: calc( REST + var(--md-sys-shape-morph-enabled, 1) * (MORPHED - REST));At 1 that is exactly MORPHED; at 0 it collapses to REST. The toggle
stays in pure CSS, so flipping it applies instantly with no re-render — and
it avoids :host-context(), which Firefox does not implement.
Which components actually morph
Section titled “Which components actually morph”Only 7 components have shape-morph behaviour; the attribute is a no-op everywhere else:
md-button · md-icon-button · md-fab · md-fab-menu ·
md-segmented-button · md-split-button · md-card
Two distinct triggers:
- Pressed — the radius squares off by one step while held.
- Selected (toggle) — a
roundcontrol morphs to square when selected; asquareone morphs to round.
Runtime toggle
Section titled “Runtime toggle”document.documentElement.dataset.shapeMorph = 'off'; // freezedocument.documentElement.dataset.shapeMorph = 'on'; // restoredelete document.documentElement.dataset.shapeMorph; // default (on)Note the DOM property is camelCased (dataset.shapeMorph) for the
data-shape-morph attribute.
Reduced motion
Section titled “Reduced motion”@media (prefers-reduced-motion: reduce) { :root { --md-sys-shape-morph-enabled: 0; }}Pair it with the same guard for ripple to cover both expressive behaviours:
@media (prefers-reduced-motion: reduce) { :root { --md-sys-ripple-enabled: 0; --md-sys-shape-morph-enabled: 0; }}When to switch it off
Section titled “When to switch it off”| Situation | Recommendation |
|---|---|
| Utilitarian / enterprise visual language | Global off |
| Brand guidelines specify static geometry | Global off |
| User prefers reduced motion | off via the media query |
| Consumer product wanting the M3 Expressive feel | Leave it on |
Related
Section titled “Related”- Ripple — the other expressive toggle, same mechanism
- Design Tokens — the shape corner scale