MotionButton
Native buttons and links with 13 independent interactions and 35 source icon combinations
Installation
import { TxMotionButton } from '@talex-touch/tuffex/motion-button'
import '@talex-touch/tuffex/base.css'
import '@talex-touch/tuffex/motion-button/style.css'
Usage
All interactions and source combinations
Filter any of the 13 interactions, inspect all 35 icon pairs, and switch between grid, list and icon-matrix layouts. Each specimen supports pointer hover, keyboard focus, native activation and decorative replay. Selected state and activation counts live only in the demo's local state; the copy specimen writes to the real clipboard.
Caller-owned operations and content
sourceId selects visual parameters, not an operation or a default label. A hover may reveal a check icon without claiming an operation succeeded. Only caller-owned selected enables activeLabel.
<script setup lang="ts">
import { ref } from 'vue'
import { TxMotionButton } from '@talex-touch/tuffex/motion-button'
const copied = ref(false)
const busy = ref(false)
const error = ref('')
const marked = ref(false)
async function copyHash() {
busy.value = true
error.value = ''
try {
await navigator.clipboard.writeText('43c29ce9cdd16459e3eab4992381b8d35b38776a')
copied.value = true
}
catch {
error.value = 'Clipboard access failed'
}
finally {
busy.value = false
}
}
</script>
<template>
<TxMotionButton source-id="4" label="Copy hash" active-label="Copied"
:selected="copied" :disabled="busy" @click="copyHash" />
<p role="status">{{ error }}</p>
<TxMotionButton variant="sparkle" label="Mark local item" :selected="marked" @click="marked = !marked">
<template #icon><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 6h16M4 12h16M4 18h16" /></svg></template>
<template #active-icon><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5" /></svg></template>
<template #default="{ selected }">{{ selected ? 'Local item marked' : 'Mark local item' }}</template>
</TxMotionButton>
</template>
The application owns its real operations and glyph content. Content slots are labels and decoration; place independent interactive controls outside the button.
Focus-blur links
Source 35 renders a group of real anchors or buttons. There is no button wrapping other interactive elements, and the component creates no placeholder destinations.
<script setup lang="ts">
import { ref } from 'vue'
import { TxMotionButton } from '@talex-touch/tuffex/motion-button'
const lastLabel = ref('')
</script>
<template>
<TxMotionButton source-id="35" label="Project links" :items="[
{ label: 'Repository', href: 'https://github.com/TalexDreamSoul/talex-touch' },
{ label: 'Documentation', href: '/docs' },
{ label: 'Local action' },
]" @select="item => lastLabel = item.label" />
<output>{{ lastLabel }}</output>
</template>
Best Practices
- Provide a meaningful
label, default slot orariaLabel. Icon-only controls still need an accessible name. - Run the real operation in
@clickor@select. SetselectedandactiveLabelonly from your own state; neither hover nor replay emits success or changes that state. - Use
hreffor navigation andtype="submit"for form submission. Buttons retain native Enter/Space activation; anchors retain native Enter activation and modifier-click navigation. - Set
disabledwhile an operation must not run. A disabled link loses its destination and tab stop, rather than leaving a decorative overlay to intercept clicks. - Prefer
sourceIdfor original combinations; overridevariant, icons and colors when your content needs different parameters. Use slots for your own glyphs. - Keep focus-blur
#itemcontent non-interactive: the owning anchor or button already handles focus and activation. - Turn off
animatedto keep content and endpoint states without decorative work. Reduced motion, hidden documents, offscreen controls and deactivated KeepAlive trees suspend animation automatically.
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
sourceId | MotionButtonSourceId | — | Original string ID "1"–"35"; chooses interaction, glyph pair, active tint/fill and retention. Never supplies a rendered label or operation. |
variant | MotionButtonVariant | Preset, otherwise morph | Explicit interaction override; all 13 values are listed below. |
label | string | '' | Caller-owned visible label; also names an icon-only control unless ariaLabel is supplied. |
activeLabel | string | — | Replaces label only while selected === true. |
ariaLabel | string | — | Accessible name override; names the group for focus-blur. |
icon | MorphIconSource | Preset | First glyph: built-in icon name, SVG path d, Lucide-style IconNode, or SVG markup supported by IconMorph. |
activeIcon | MorphIconSource | Preset, otherwise icon | Target glyph for pair interactions. |
iconColor | string | Preset, otherwise currentColor | First-icon active tint for pulse/shake and fallback target tint. Resting glyphs inherit the control's ink. |
activeIconColor | string | Preset, otherwise iconColor | Immediate target-glyph tint; CSS color/token value. |
activeFill | boolean | Preset, otherwise false | Fills an active glyph in pulse/color-morph/morph. |
size | 'xs' | 'sm' | 'md' | 'lg' | md | Heights 24/30/36/42 px; size vocabulary is shared with other TuffEx controls. |
disabled | boolean | false | Native disabled button, or destination-less, non-tabbable disabled link. Also disables every focus-blur item. |
animated | boolean | true | Enables decoration when the shared lifecycle gate is active. |
selected | boolean | — | Caller-owned target-icon state; sets aria-pressed on a button. Does not execute or acknowledge an operation. |
href | string | — | Renders a native anchor instead of a button. |
target | string | — | Native anchor target. |
rel | string | noopener noreferrer for _blank | Native anchor relationship; explicit value wins. |
type | 'button' | 'submit' | 'reset' | button | Native button type; not applied to anchors. |
iconOnly | boolean | false | Square control without visible label; preserves accessible naming. |
hoverBackground | string | var(--tx-fill-color) | Immediate interaction fill. No hover color tween. |
holdDuration | number | Preset, otherwise 0 | Decorative target-icon retention after exit, in ms. IDs 4/21/22/24/25 retain for 500 ms; never retains a success label. |
spring | Transition | Source-specific | Explicit override for geometry springs. Defaults: outer layout 500/25; ordinary icons 600/25; rotate/text-reveal 400/25; expand-ring 400/20; focus brackets 350/20; notification dot 600/15. Also accepts duration/ease; physical coefficients are forwarded to IconMorph. |
magneticStrength | number | 0.35 | Pointer offset multiplier for magnetic pull. |
magneticRange | number | — | Optional distance limit in px. Undefined preserves the catalog's unrestricted pull inside the button. |
magneticSpring | SpringConfig | { stiffness: 500, damping: 25 } | Shared per-frame spring for magnetic travel and return. |
items | readonly MotionButtonItem[] | [] | Caller-owned links/buttons for focus-blur. Each item has label, optional href, target, rel, disabled. |
blurAmount | number | 4 | Focus-blur sibling blur in px. |
opacityAmount | number | 0.4 | Focus-blur sibling opacity, clamped to 0–1. |
showBrackets | boolean | true | Focus-blur dashed outline around the hovered/focused item. |
Native attributes such as id, name, value, form, download, aria-expanded and aria-controls pass through to the owning element. Focus-blur attributes apply to its group.
Events
| Event | Payload | Description |
|---|---|---|
click | MouseEvent | Native activation of a non-disabled normal button/link. No implicit state change or delayed business action. |
select | (item: MotionButtonItem, index: number, event: MouseEvent) | Native activation of a non-disabled focus-blur item. Navigation proceeds unless the caller prevents it. |
Slots
| Slot | Scope | Description |
|---|---|---|
default | { active, selected, disabled } | Visible label/content; fallback is label through TextMorph. Empty focus-blur groups also expose this fallback. |
icon | { active, selected } | Custom first glyph. In morph/color-morph, a single scoped icon slot can render from current active state. |
active-icon | { active, selected } | Custom second glyph for slide-arrow, sparkle, ring, morph and color-morph. Paired slots preserve the original movement/scale switch. |
reveal | { active } | Second, visually revealed text row for text-reveal; decorative and excluded from the accessible name. Defaults to the same label. |
item | { item, index, active } | Focus-blur item label; place no nested interactive controls in this slot. |
Exposed Methods
| Method | Signature | Description |
|---|---|---|
replay | () => void | Re-enters the visual trajectory without emitting click/select or changing selected. Replays magnetic pull/return and focus-blur emphasis too; inactive/disabled controls do not start work. |
focus | () => void | Focuses the native control, or the first enabled focus-blur item. |
CSS Variables
| Variable | Default | Description |
|---|---|---|
--tx-motion-button-height | 24/30/36/42 px | Size-tier control height. |
--tx-motion-button-pad | 12/16/24/28 px | Size-tier horizontal padding. |
--tx-motion-button-duration | Resolved icon/bracket spring | Icon/bracket geometry transition time; zero while inactive. |
--tx-motion-button-ease | Resolved icon/bracket spring | Compiled spring easing for icon/bracket geometry and opacity. |
--tx-motion-button-layout-duration | Resolved 500/25 spring | Outer padding/scale transition time. |
--tx-motion-button-layout-ease | Resolved 500/25 spring | Independent outer geometry easing. |
--tx-motion-button-dot-duration | Resolved 600/15 spring | Ring notification-dot transition time. |
--tx-motion-button-dot-ease | Resolved 600/15 spring | Independently tuned notification-dot easing. |
--tx-motion-button-hover-bg | hoverBackground | Instant interaction background. |
--tx-motion-button-icon-color | Resolved target tint | Active shake-label tint; glyph tint/fill is also applied immediately. |
--tx-motion-button-blur | blurAmount | Focus-blur sibling filter. |
--tx-motion-button-dim | opacityAmount | Focus-blur sibling opacity. |
Types
Exports include MotionButtonProps, MotionButtonEmits, MotionButtonInstance, MotionButtonItem, MotionButtonSize, MotionButtonVariant, MotionButtonSourceId, MotionButtonCatalogEntry and TxMotionButtonInstance. MotionButton is installable; TxMotionButton is the Vue component. MOTION_BUTTON_VARIANTS, MOTION_BUTTON_SOURCE_IDS, MOTION_BUTTON_CATALOG and MOTION_BUTTON_PRESETS expose the actual enumerations and source metadata.
| Interaction | Independent trajectory |
|---|---|
slide-arrow | First icon exits 10 px left; label keeps its place as the right icon enters from 10 px right. Width/gap follows the 600/25 spring. |
sparkle | First icon exits upward 15 px at 0.8 scale; target enters from below. Two separate star particles unwind from −45°/+45°, delayed 50/100 ms. |
morph | Each original stroke pair uses IconMorph's vector geometry; a 0.5-to-1 scale/opacity entrance keeps the source switch rhythm. |
color-morph | Bookmark/thumb/star keeps its own outline and switches immediate tint plus filled state; the source scale/opacity beat remains. |
pulse | Heart expands 1 → 1.25 → 1 over 400 ms, with immediate active fill/tint. |
rotate | Settings or reload glyph rotates 180° with a 400/25 spring and returns on exit. |
shake | Trash moves 0/−2/0/−2/0 px vertically with 0/−10°/10°/−10°/0° rotation over 400 ms. |
ring | Bell switches to BellRing via −15°/15°, 0.8-scale icon poses; a separate 6 px notification dot uses its own 600/15 spring after 100 ms. |
glare | A 50 px, −20° shine travels −150% → 150% in 850 ms with a 1 s repeat gap, only while interacting. |
text-reveal | Arrow rotates 45°; two 18 px text rows scroll upward one row with a 400/25 spring. |
magnetic | Pointer offset × strength pulls the control; one shared spring carries velocity and returns it to the same origin. |
expand-ring | Glyph scales to 1.1; the separate outline expands 1 → 1.15 and fades out over 600 ms. |
focus-blur | Other real links/buttons blur and dim; the active item's dashed brackets use a 350/20 spring from 1.3 → 1.1 scale. |
Overview
- Explicit
variant, icons, colors andholdDurationoverridesourceId. Source metadata labels are attribution only and never become implicit application copy. - Hover, focus, pointer press and replay drive decoration.
selectedholds a target icon and enablesactiveLabel; it remains caller-owned. The original hover-based “Copied” business claim is intentionally not ported. - Decoration uses pointer-inert content/layers. Normal controls remain native buttons/anchors, and focus-blur items remain individual native controls with visible keyboard outlines. Disabled anchors have no
href, no tab stop and no emitted activation. - Native form submission/reset and link navigation are not replaced by JavaScript keyboard emulation. Enter/Space pressed decoration follows the native element's supported keys.
- One shared activity gate stops CSS loops, pending replay frames, retention/replay timers, magnetic RAF work and icon/text morph work when inactive. SSR touches no browser APIs. Reduced motion keeps readable content and static endpoint state.
- Replay is a visual operation: it paints a rest pose before re-entering the same path. Magnetic replay samples the control's size once and uses the same pull/return spring; focus-blur replay emphasizes the first enabled item.
Technologies
- Upstream: Amicro, MIT, Copyright (c) 2026 SYED SUBHAN UDDIN.
- Behavioral sources:
src/components/AnimatedButton.tsx:39–415,src/data/buttons.tsx:43–77,src/components/cards/FocusBlur.tsx:17–73. Registryhover/magnetic-button.tsxandhover/glow-button.tsxwere reviewed; their standalone registry variants belong to the Motion interaction family. - Original glyphs come from the upstream lockfile's pinned lucide-react 0.546.0. All 46 SVG icons retain their source nodes and geometry attributes; only React keys are omitted. The ISC and Feather-derived MIT notices are preserved in full in
icons.ts's@licenseheader. - TuffEx implementation:
motion-button/src/TxMotionButton.vue,catalog.ts,icons.tsandMotionButtonGlyph.vue. Stroke changes reuse IconMorph, value changes reuse TextMorph, and physics/lifecycle reuse the existing shared spring anduseMotionActivity. An activity-boundary key destroys the old morph controller rather than merely changing its reduced-motion flag. - The table below preserves all 35 actual combinations; these IDs change glyphs, interaction parameters, fills and retention, not just labels. The fixed labels are source metadata; the caller supplies rendered copy.
| Source ID | Source combination | Interaction | Glyphs | Source |
|---|---|---|---|---|
1 | Download for Mac | slide-arrow | Apple → ArrowRight | buttons.tsx:43 |
2 | Star on GitHub | sparkle | GitHub → Star | buttons.tsx:44 |
3 | Deploy App | morph | Cloud → CloudUpload | buttons.tsx:45 |
4 | Copy Hash | morph | Copy → Check; 500 ms icon retention | buttons.tsx:46 |
5 | Sponsor | pulse | Heart; active fill | buttons.tsx:47 |
6 | Share | morph | Link → Send | buttons.tsx:48 |
7 | Preview | morph | Play → Pause | buttons.tsx:49 |
8 | Settings | rotate | Settings | buttons.tsx:50 |
9 | Delete | shake | Trash2 | buttons.tsx:51 |
10 | Subscribe | ring | Bell → BellRing | buttons.tsx:52 |
11 | Search | morph | Search → X | buttons.tsx:53 |
12 | Theme | morph | Moon → Sun | buttons.tsx:54 |
13 | Microphone | morph | Mic → MicOff | buttons.tsx:55 |
14 | Camera | morph | Video → VideoOff | buttons.tsx:56 |
15 | Volume | morph | Volume2 → VolumeX | buttons.tsx:57 |
16 | Lock | morph | Lock → Unlock | buttons.tsx:58 |
17 | Directory | morph | Folder → FolderOpen | buttons.tsx:59 |
18 | Visibility | morph | Eye → EyeOff | buttons.tsx:60 |
19 | Save Later | color-morph | Bookmark outline → filled | buttons.tsx:61 |
20 | Like | color-morph | ThumbsUp outline → filled | buttons.tsx:62 |
21 | Download | morph | Download → Check; 500 ms icon retention | buttons.tsx:63 |
22 | Upload | morph | Upload → Check; 500 ms icon retention | buttons.tsx:64 |
23 | Account | morph | User → UserCheck | buttons.tsx:65 |
24 | Submit | morph | Send → Check; 500 ms icon retention | buttons.tsx:66 |
25 | Edit | morph | Pen → Check; 500 ms icon retention | buttons.tsx:67 |
26 | Network | morph | Wifi → WifiOff | buttons.tsx:68 |
27 | Power | morph | Battery → BatteryCharging | buttons.tsx:69 |
28 | Expand | morph | Maximize → Minimize | buttons.tsx:70 |
29 | Reload | rotate | RefreshCw | buttons.tsx:71 |
30 | Favorite | color-morph | Star outline → filled | buttons.tsx:72 |
31 | Glare Shine | glare | Star with sweeping shine | buttons.tsx:73 |
32 | Text Reveal | text-reveal | ArrowRight and two text rows | buttons.tsx:74 |
33 | Magnetic Field | magnetic | GitHub and pointer pull | buttons.tsx:75 |
34 | Expand Ring | expand-ring | Link and separate expanding outline | buttons.tsx:76 |
35 | Focus Blur Links | focus-blur | Caller-owned link/button group | buttons.tsx:77 |