Components / Interactive

Pressable

Pressable is the interactive atom — a Box that handles activation. It carries the accessibility contract and no visual opinion, so you bring the styling and it brings the behaviour.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A Box that renders a <button> by default and adds the interactive contract: a cross-platform onPress handler, a cursor that reflects the disabled state, aria-disabled mirroring, and click suppression while disabled. The pseudo-state props — _hover, _focus, _active, _disabled — come from Box and work exactly as they do everywhere else.

Pressable styles nothing. No colour, no padding, no border — it is the surface Button and IconButton put their visual layer on. Reach for Pressable directly when you need an interactive element that does not look like a button: a clickable card, a custom toggle, a tappable list row.

Install

Pressable is exported from usemotif. No separate install.

example.tsx
import { Pressable } from 'usemotif';

API

Pressable

stable
function Pressable(props: PressableProps): JSX.Element
onPress(event: MouseEvent<HTMLElement>) => void

Cross-platform activation handler. Fires on mouse, keyboard, or touch. Suppressed while `disabled`.

disabledboolean

Disables interaction. Sets the native `disabled` attribute on `<button>` and `aria-disabled` on every surface, and suppresses `onPress`.

asElementType= "button"

Element to render. `as="a"` for a link-shaped pressable; any element for a custom tap target.

…BoxPropsBoxProps

Every Box prop — style props, responsive keys, and the pseudo-state bags `_hover` / `_focus` / `_active` / `_disabled`.

Accessibility

The default <button> is the accessible default — it is focusable, it activates on Enter and Space, and it is announced as a button. Keep it unless you have a specific reason to change as.

When you do change as, the contract gets thinner. Pressable still mirrors disabled to aria-disabled so the disabled visuals apply — but a non-button element is not focusable and does not activate on the keyboard on its own. If you render as="div", you take on tabIndex, role, and the keyboard handlers yourself. Disabled styling lives in the _disabled bag; the component sets the attribute, not the appearance.

Examples

A styled pressable surface:

example.tsx
<Pressable
onPress={save}
bg="$colors.action.primary.bg"
color="$colors.action.primary.fg"
px="$4"
py="$2"
borderRadius="$radii.md"
_hover={{ opacity: 0.9 }}
_active={{ opacity: 0.8 }}
_disabled={{ opacity: 0.5 }}
>
Save
</Pressable>

A clickable card:

example.tsx
<Pressable
as="article"
onPress={() => open(item.id)}
p="$4"
borderRadius="$radii.md"
borderWidth={1}
borderColor="$colors.line.base"
_hover={{ borderColor: '$colors.accent.base' }}
>
<Heading level={4}>{item.title}</Heading>
<Text>{item.summary}</Text>
</Pressable>