27.7k

Pressable Feedback

A press interaction layer adding ripple, highlight, hold-to-confirm, and progress feedback effects to any element

Import

import { PressableFeedback } from '@darkcode-ui/react';

Anatomy

Import the PressableFeedback component and access all parts using dot notation. The root renders a button by default and exposes hover / press / focus state through data attributes, so the feedback layers can compose freely on top of any content.

<PressableFeedback>
  <PressableFeedback.Highlight />
  <PressableFeedback.Ripple />
  <PressableFeedback.HoldConfirm />
  <PressableFeedback.ProgressFeedback />
</PressableFeedback>

Usage

import {PressableFeedback} from "@darkcode-ui/react";

export function PressableFeedbackDefault() {
  return (
    <PressableFeedback className="relative flex min-w-40 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground">

Comparison

import {PressableFeedback} from "@darkcode-ui/react";

const surface =
  "relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";

Disabled

import {PressableFeedback} from "@darkcode-ui/react";

const surface =
  "relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";

With Highlight

import {PressableFeedback} from "@darkcode-ui/react";

export function PressableFeedbackWithHighlight() {
  return (
    <PressableFeedback className="relative flex min-w-40 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground">
      <PressableFeedback.Highlight />
      <span className="relative z-[1]">Highlight</span>
    </PressableFeedback>
  );
}

Standalone Highlight

import {PressableFeedback} from "@darkcode-ui/react";

export function PressableFeedbackStandaloneHighlight() {
  return (
    <PressableFeedback className="relative flex size-16 items-center justify-center rounded-2xl bg-accent text-accent-foreground">
      <PressableFeedback.Highlight />
      <span className="relative z-[1] text-lg">★</span>
    </PressableFeedback>
  );
}

With Ripple

import {PressableFeedback} from "@darkcode-ui/react";

export function PressableFeedbackWithRipple() {
  return (
    <PressableFeedback className="relative flex min-w-40 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground">
      <PressableFeedback.Ripple />
      <span className="relative z-[1]">Ripple</span>
    </PressableFeedback>
  );
}

Standalone Ripple

import {PressableFeedback} from "@darkcode-ui/react";

export function PressableFeedbackStandaloneRipple() {
  return (
    <PressableFeedback className="relative flex size-16 items-center justify-center rounded-full border border-border bg-surface text-foreground">
      <PressableFeedback.Ripple />
      <span className="relative z-[1] text-lg">+</span>
    </PressableFeedback>
  );
}

With Hold Confirm

import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";

export function PressableFeedbackWithHoldConfirm() {
  return (

Hold Confirm Callback

"use client";

import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";

Hold Confirm Durations

import {PressableFeedback} from "@darkcode-ui/react";

const surface =
  "relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";

Hold Confirm Sweep

import type {ComponentProps} from "react";

import {PressableFeedback} from "@darkcode-ui/react";

type Sweep = NonNullable<ComponentProps<typeof PressableFeedback.HoldConfirm>["sweep"]>;

With Progress Feedback

import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";

export function PressableFeedbackWithProgressFeedback() {
  return (

Progress Feedback Callback

"use client";

import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";

Progress Feedback Durations

import {PressableFeedback} from "@darkcode-ui/react";

const surface =
  "relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";

Progress Feedback No Reset

import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";

export function PressableFeedbackProgressFeedbackNoReset() {
  return (

Progress Feedback Sweep

import type {ComponentProps} from "react";

import {PressableFeedback} from "@darkcode-ui/react";

type Sweep = NonNullable<ComponentProps<typeof PressableFeedback.ProgressFeedback>["sweep"]>;

Pressable Cards

import {PressableFeedback} from "@darkcode-ui/react";

const plans = [
  {desc: "For individuals getting started", title: "Starter"},
  {desc: "For growing teams that need more", title: "Pro"},

Styling

Passing Tailwind CSS classes

The root and every feedback layer accept a className. Keep your own content above the overlays with relative z-[1]:

import {PressableFeedback} from '@darkcode-ui/react';

function CustomPressable() {
  return (
    <PressableFeedback className="rounded-2xl border border-border bg-surface px-6 py-4">
      <PressableFeedback.Highlight />
      <PressableFeedback.Ripple />
      <span className="relative z-[1]">Press me</span>
    </PressableFeedback>
  );
}

Customizing the component classes

To customize the PressableFeedback classes, you can use the @layer components directive.
Learn more.

@layer components {
  .pressable-feedback {
    --pressable-feedback-highlight-color: var(--accent);
    --pressable-feedback-highlight-opacity: 0.12;
  }

  .pressable-feedback__hold-confirm {
    background-color: var(--danger);
    color: var(--danger-foreground);
  }
}

DarkCode UI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

The PressableFeedback component uses these CSS classes (View source styles):

Base Classes

  • .pressable-feedback - Base pressable container with relative positioning and overflow hidden

Element Classes

  • .pressable-feedback__highlight - Opacity-based press overlay
  • .pressable-feedback__ripple - M3-style radial ripple container
  • .pressable-feedback__ripple-surface - Ripple animation surface with ::before (hover) and ::after (press) pseudo-elements
  • .pressable-feedback__hold-confirm - Clip-path hold-to-reveal overlay
  • .pressable-feedback__progress-feedback - Clip-path click-to-progress overlay

Interactive States

  • Focus visible: :focus-visible or [data-focus-visible="true"] on .pressable-feedback (focus ring)
  • Disabled: :disabled or [aria-disabled="true"] on .pressable-feedback (reduced opacity, no pointer events)
  • Hover: :hover or [data-hovered="true"] on the root activates .pressable-feedback__highlight opacity
  • Pressed: :active or [data-pressed="true"] on the root activates the pressed opacity

Data Attributes

  • [data-sweep="right|left|down|up"] on hold-confirm and progress-feedback - Clip-path sweep direction
  • [data-holding="true"] on .pressable-feedback__hold-confirm - Currently being held
  • [data-complete="true"] on hold-confirm/progress-feedback - Action completed
  • [data-progressing="true"] on .pressable-feedback__progress-feedback - Progress is active

CSS Variables

  • --pressable-feedback-highlight-color - Highlight overlay color (default: currentColor)
  • --pressable-feedback-highlight-opacity - Hover opacity (default: 0.08)
  • --pressable-feedback-highlight-pressed-opacity - Pressed opacity (default: 0.12)
  • --pressable-feedback-ripple-color - Ripple color (default: currentColor)
  • --pressable-feedback-ripple-hover-opacity - Ripple hover opacity (default: 0.08)
  • --pressable-feedback-ripple-pressed-opacity - Ripple pressed opacity (default: 0.12)
  • --pressable-feedback-hold-confirm-duration - Hold duration (default: 2000ms)
  • --pressable-feedback-hold-confirm-release-duration - Release snap-back duration (default: 200ms)
  • --pressable-feedback-progress-feedback-duration - Progress duration (default: 2000ms)
  • --pressable-feedback-progress-feedback-release-duration - Reset snap-back duration (default: 300ms)

API Reference

PressableFeedback

The root pressable container. Renders a button element by default.

PropTypeDefaultDescription
isDisabledbooleanfalseWhether the pressable is disabled
childrenReactNode-Feedback layers and content
classNamestring-Additional CSS class
renderDOMRenderFunction-Custom render function to override the default button element

Also supports all native button HTML attributes.

PressableFeedback.Highlight

Opacity-based hover/press overlay. No additional props beyond standard span attributes.

PressableFeedback.Ripple

M3-style radial ripple effect.

PropTypeDefaultDescription
durationnumber150Duration in ms for the ripple grow animation
hoverOpacitynumber0.08Opacity of the hover state
pressedOpacitynumber0.12Opacity of the pressed state
minimumPressDurationnumber225Minimum press duration in ms
isDisabledboolean-Whether the ripple is disabled
classNamestring-Additional CSS class
styleCSSProperties-Additional inline styles

PressableFeedback.HoldConfirm

Clip-path hold-to-reveal overlay.

PropTypeDefaultDescription
durationnumber2000Hold duration in ms before the action is confirmed
releaseDurationnumber200Duration in ms for the snap-back animation on release
sweep'right' | 'left' | 'down' | 'up''right'Which edge the clip-path reveal sweeps toward
resetOnCompletebooleantrueWhether to reset the overlay after the hold completes
isDisabledboolean-Whether the hold confirm is disabled
onComplete() => void-Fired when the hold reaches the full duration
childrenReactNode-Overlay content shown during the reveal
classNamestring-Additional CSS class

PressableFeedback.ProgressFeedback

Clip-path click-to-progress overlay (auto, no hold required).

PropTypeDefaultDescription
durationnumber2000Progress duration in ms before the action is confirmed
releaseDurationnumber300Duration in ms for the snap-back animation on reset
sweep'right' | 'left' | 'down' | 'up''right'Which edge the clip-path reveal sweeps toward
autoResetbooleantrueWhether to automatically reset after completing
resetDelaynumber1500Delay in ms before resetting after completion
isDisabledboolean-Whether the progress feedback is disabled
onComplete() => void-Fired when the progress reaches the full duration
onReset() => void-Fired when the overlay resets back to idle
childrenReactNode-Overlay content shown during the reveal
classNamestring-Additional CSS class

On this page