# Pressable Feedback **Category**: react **URL**: https://ui.darkcode.dev/en/docs/react/components/pressable-feedback **Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/pressable-feedback.mdx > A press interaction layer adding ripple, highlight, hold-to-confirm, and progress feedback effects to any element *** ## Import ```tsx 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. ```tsx ``` ### Usage ```tsx import {PressableFeedback} from "@darkcode-ui/react"; export function PressableFeedbackDefault() { return ( Press me ); } ``` ### Comparison ```tsx 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"; export function PressableFeedbackComparison() { return (
Highlight Ripple Both
); } ``` ### Disabled ```tsx 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"; export function PressableFeedbackDisabled() { return (
Enabled Disabled
); } ``` ### With Highlight ```tsx import {PressableFeedback} from "@darkcode-ui/react"; export function PressableFeedbackWithHighlight() { return ( Highlight ); } ``` ### Standalone Highlight ```tsx import {PressableFeedback} from "@darkcode-ui/react"; export function PressableFeedbackStandaloneHighlight() { return ( ); } ``` ### With Ripple ```tsx import {PressableFeedback} from "@darkcode-ui/react"; export function PressableFeedbackWithRipple() { return ( Ripple ); } ``` ### Standalone Ripple ```tsx import {PressableFeedback} from "@darkcode-ui/react"; export function PressableFeedbackStandaloneRipple() { return ( ); } ``` ### With Hold Confirm ```tsx import {PressableFeedback} from "@darkcode-ui/react"; import {Icon} from "@iconify/react"; export function PressableFeedbackWithHoldConfirm() { return ( Hold to confirm Confirmed ); } ``` ### Hold Confirm Callback ```tsx "use client"; import {PressableFeedback} from "@darkcode-ui/react"; import {Icon} from "@iconify/react"; import {useState} from "react"; export function PressableFeedbackHoldConfirmCallback() { const [count, setCount] = useState(0); return (
Hold to delete setCount((c) => c + 1)}> Deleted

Confirmed {count} times

); } ``` ### Hold Confirm Durations ```tsx 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"; const durations = [ {label: "Fast · 800ms", value: 800}, {label: "Default · 2s", value: 2000}, {label: "Slow · 3.5s", value: 3500}, ]; export function PressableFeedbackHoldConfirmDurations() { return (
{durations.map(({label, value}) => ( {label} ))}
); } ``` ### Hold Confirm Sweep ```tsx import type {ComponentProps} from "react"; import {PressableFeedback} from "@darkcode-ui/react"; type Sweep = NonNullable["sweep"]>; const surface = "relative flex min-w-32 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground capitalize"; const sweeps: Sweep[] = ["right", "left", "up", "down"]; export function PressableFeedbackHoldConfirmSweep() { return (
{sweeps.map((sweep) => ( {sweep} ))}
); } ``` ### With Progress Feedback ```tsx import {PressableFeedback} from "@darkcode-ui/react"; import {Icon} from "@iconify/react"; export function PressableFeedbackWithProgressFeedback() { return ( Click to progress Done ); } ``` ### Progress Feedback Callback ```tsx "use client"; import {PressableFeedback} from "@darkcode-ui/react"; import {Icon} from "@iconify/react"; import {useState} from "react"; export function PressableFeedbackProgressFeedbackCallback() { const [status, setStatus] = useState<"idle" | "done">("idle"); return (
Click to save setStatus("done")} onReset={() => setStatus("idle")} > Saved

Status: {status}

); } ``` ### Progress Feedback Durations ```tsx 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"; const durations = [ {label: "Fast · 800ms", value: 800}, {label: "Default · 2s", value: 2000}, {label: "Slow · 3.5s", value: 3500}, ]; export function PressableFeedbackProgressFeedbackDurations() { return (
{durations.map(({label, value}) => ( {label} ))}
); } ``` ### Progress Feedback No Reset ```tsx import {PressableFeedback} from "@darkcode-ui/react"; import {Icon} from "@iconify/react"; export function PressableFeedbackProgressFeedbackNoReset() { return ( Click once Completed ); } ``` ### Progress Feedback Sweep ```tsx import type {ComponentProps} from "react"; import {PressableFeedback} from "@darkcode-ui/react"; type Sweep = NonNullable["sweep"]>; const surface = "relative flex min-w-32 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground capitalize"; const sweeps: Sweep[] = ["right", "left", "up", "down"]; export function PressableFeedbackProgressFeedbackSweep() { return (
{sweeps.map((sweep) => ( {sweep} ))}
); } ``` ### Pressable Cards ```tsx import {PressableFeedback} from "@darkcode-ui/react"; const plans = [ {desc: "For individuals getting started", title: "Starter"}, {desc: "For growing teams that need more", title: "Pro"}, {desc: "For organizations at scale", title: "Enterprise"}, ]; export function PressableFeedbackPressableCards() { return (
{plans.map((plan) => ( {plan.title} {plan.desc} ))}
); } ``` ## 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]`: ```tsx import {PressableFeedback} from '@darkcode-ui/react'; function CustomPressable() { return ( Press me ); } ``` ### Customizing the component classes To customize the PressableFeedback classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @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](https://getbem.com/) 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](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/pressable-feedback.css)): #### 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. | Prop | Type | Default | Description | |------|------|---------|-------------| | `isDisabled` | `boolean` | `false` | Whether the pressable is disabled | | `children` | `ReactNode` | - | Feedback layers and content | | `className` | `string` | - | Additional CSS class | | `render` | `DOMRenderFunction` | - | 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. | Prop | Type | Default | Description | |------|------|---------|-------------| | `duration` | `number` | `150` | Duration in ms for the ripple grow animation | | `hoverOpacity` | `number` | `0.08` | Opacity of the hover state | | `pressedOpacity` | `number` | `0.12` | Opacity of the pressed state | | `minimumPressDuration` | `number` | `225` | Minimum press duration in ms | | `isDisabled` | `boolean` | - | Whether the ripple is disabled | | `className` | `string` | - | Additional CSS class | | `style` | `CSSProperties` | - | Additional inline styles | ### PressableFeedback.HoldConfirm Clip-path hold-to-reveal overlay. | Prop | Type | Default | Description | |------|------|---------|-------------| | `duration` | `number` | `2000` | Hold duration in ms before the action is confirmed | | `releaseDuration` | `number` | `200` | Duration in ms for the snap-back animation on release | | `sweep` | `'right' \| 'left' \| 'down' \| 'up'` | `'right'` | Which edge the clip-path reveal sweeps toward | | `resetOnComplete` | `boolean` | `true` | Whether to reset the overlay after the hold completes | | `isDisabled` | `boolean` | - | Whether the hold confirm is disabled | | `onComplete` | `() => void` | - | Fired when the hold reaches the full duration | | `children` | `ReactNode` | - | Overlay content shown during the reveal | | `className` | `string` | - | Additional CSS class | ### PressableFeedback.ProgressFeedback Clip-path click-to-progress overlay (auto, no hold required). | Prop | Type | Default | Description | |------|------|---------|-------------| | `duration` | `number` | `2000` | Progress duration in ms before the action is confirmed | | `releaseDuration` | `number` | `300` | Duration in ms for the snap-back animation on reset | | `sweep` | `'right' \| 'left' \| 'down' \| 'up'` | `'right'` | Which edge the clip-path reveal sweeps toward | | `autoReset` | `boolean` | `true` | Whether to automatically reset after completing | | `resetDelay` | `number` | `1500` | Delay in ms before resetting after completion | | `isDisabled` | `boolean` | - | 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 | | `children` | `ReactNode` | - | Overlay content shown during the reveal | | `className` | `string` | - | Additional CSS class |