GUILD OF GLEKS UIv21.6.1

gogRipple

Ripple 21.6.1

A pointer-position wash that grows from where you pressed and fades when you let go. A directive, not a component, so it goes on any element you already have — no wrapper, no layout change. Built from scratch: there is no CDK in this library.

It is off by default

21.6.1 wires the ripple into nine of the library's own components and ships it switched off. If you copy an example from this page onto a gog-button and see nothing happen, you have not made a mistake — the app-wide switch is off. Turn it on once:

typescript
import { provideGogConfig } from '@guildofgleks/ui';

export const appConfig: ApplicationConfig = {
  providers: [
    provideGogConfig({ ripple: { enabled: true } }),
  ],
};

Adding the ripple to the library therefore changed the look of nothing. The demos on this page are the gogRipple directive on the page's own markup, which is always explicit and needs no switch.

Overview

Import the directive and put it on the element you want pressed. Press and hold: the wave grows and stays until you let go.

typescript
import { GogRippleDirective } from '@guildofgleks/ui';

@Component({
  // ...
  imports: [GogRippleDirective],
})
A pill
Square corners
A larger tile, so the wave has room to travel
<div class="row">
  <button class="surface" gogRipple>Plain button</button>
  <div class="surface surface--pill" gogRipple>A pill</div>
  <div class="surface surface--square" gogRipple>Square corners</div>
  <div class="surface surface--tile" gogRipple>A larger tile, so the wave has room to travel</div>
</div>

Examples

Put it on the element that paints the surface

The one sharp edge in this API. The wash lives in its own layer, and that layer takes its corner radius from its host with border-radius: inherit — exact, per instance, per theme, with no token to declare. The cost is that on a wrapper whose child paints the rounded background, the layer inherits the wrapper's radius — very often 0 — and the wash squares off at the corners.

Both buttons below are ripple-wrapped; only the second has the directive on the element that actually has the radius. Press each and watch the corners.

<div class="row">
  <!-- Wrong: the wrapper has no radius, so the layer inherits 0 and the wash squares off. -->
  <span class="wrapper" gogRipple>
    <button class="surface surface--pill">On the wrapper</button>
  </span>

  <!-- Right: the directive sits on the element that actually paints the rounded background. -->
  <span class="wrapper">
    <button class="surface surface--pill" gogRipple>On the surface</button>
  </span>
</div>

The library's own components never make you think about this — each opts its own inner element in. The rule applies only to markup you wrote yourself.

It does not clip a badge

This is the reason the ripple is not simply overflow: hidden on the host. gogBadge pins its badge outside the host's box with negative insets, so a host that clips its overflow clips the badge with it. The wash lives in a layer that clips itself instead, and the host is never given overflow: hidden at all — press these and the badge stays whole.

<div class="row">
  <button class="surface" gogRipple gogBadge="8" badgeAriaLabel="8 unread">Inbox</button>
  <button class="surface surface--pill" gogRipple gogBadge badgeDot>Dot, top-end</button>
  <button class="surface" gogRipple gogBadge="99+" badgePosition="bottom-start" badgeVariant="info">
    Bottom-start
  </button>
</div>

rippleCentred and rippleDisabled

Keyboard activation is always centred — Enter and Space carry no coordinates — so tab to a button and hold a key to see what centred looks like. rippleCentred makes the pointer behave the same way. rippleDisabled detaches the listeners and drops the host class rather than ignoring events, so a switched-off ripple costs nothing at all.

<div class="row">
  <button class="surface" gogRipple [rippleCentred]="centred()">
    rippleCentred: {{ centred() }}
  </button>
  <gog-button variant="outline" size="sm" (gogClick)="toggleCentred()">
    Toggle rippleCentred
  </gog-button>
</div>

<div class="row">
  <button class="surface" gogRipple [rippleDisabled]="disabled()">
    rippleDisabled: {{ disabled() }}
  </button>
  <gog-button variant="outline" size="sm" (gogClick)="toggleDisabled()">
    Toggle rippleDisabled
  </gog-button>
</div>

Suppressed where a press means nothing

Four things suppress it, none of which you wire up: rippleDisabled, a host carrying disabled, a host carrying aria-disabled="true", and prefers-reduced-motion: reduce. Answering a press the control will not act on is worse than staying still.

Reduced motion suppresses it outright rather than shortening it — turn on your system's "reduce motion" setting and none of the demos on this page produce anything.

aria-disabled
<div class="row">
  <button class="surface" gogRipple disabled>disabled</button>
  <div class="surface" gogRipple aria-disabled="true">aria-disabled</div>
</div>

Theming

Five tokens, and all five are ordinary inherited custom properties — so setting one on the element, or on anything above it, is the per-instance override. There is no variant tier and no ::ng-deep.

--gog-ripple-color defaults to currentColor rather than to a palette token. It is the only token in the catalogue that does, and it is why no per-variant tier is needed: the wash reads as the surface's own foreground on a filled button and a ghost one alike, in every theme.

Accent-coloured wash
Higher opacity
Slower
<div class="row">
  <div class="surface surface--accent" gogRipple>Accent-coloured wash</div>
  <div class="surface surface--strong" gogRipple>Higher opacity</div>
  <div class="surface surface--slow" gogRipple>Slower</div>
</div>

The library's own components

You do not put gogRipple on a gog-* component: each one already owns the element that paints its surface, so it wires its own. Turning on GOG_CONFIG.ripple.enabled gives all nine at once:

  • gog-button
  • [gogButton]
  • gog-button-toggle-group
  • gog-chip
  • gog-tabs
  • gog-accordion
  • gogCollapsibleTrigger
  • gogMenuItem
  • gog-select / gog-multiselect / gog-autocomplete options

Each also takes a ripple input 21.6.1 that wins over the app-wide setting in both directions — one instance can opt out of an app-wide on, or opt in without the app switching over.

This is the first visual default that lives in GOG_CONFIG rather than in theme.css, which is a deliberate exception to that boundary: a token could hide the wash, but the element, its listeners and its frames would still be paid for. A real "off" has to reach the TypeScript.

API Reference

Inputs

NameTypeDefaultDescription
rippleDisabled21.6.1booleanfalseSwitches the effect off. Detaches the listeners and drops the host class rather than ignoring events, so a disabled ripple costs nothing.
rippleCentred21.6.1booleanfalseStarts the wave from the middle instead of from the pointer. Keyboard activation is always centred, because a key press carries no coordinates.

Style tokens

TokenDescription
--gog-ripple-colorThe wash. Defaults to currentColor rather than a palette token — the only token in this catalogue that does — so it reads as the surface’s own foreground on a filled surface and a ghost one alike, which is why the ripple needs no per-variant tier.
--gog-ripple-opacityPeak opacity of the wash.
--gog-ripple-enter-durationHow long the wave takes to expand from the press point.
--gog-ripple-exit-durationHow long it fades once the pointer is released.
--gog-ripple-easingEasing curve for both phases.