Pointer-driven Fluent-style border, hover, and click reveal effects for Svelte.
The library exposes 3 actions:
revealContainerrevealBorderrevealItemIt uses fail-fast runtime checks and throws descriptive errors when DOM/action contracts are violated.
npm i fluent-reveal-svelte
Also import the reveal CSS once in your app entry or root layout.
import 'fluent-reveal-svelte/styles/reveal.css'
If your package setup uses a different CSS path, import the bundled reveal.css from this repo.
A minimal consumer app is available at examples/package-minimal.
It installs fluent-reveal-svelte from npm and enables border, hover, focus, and click effects on 5 buttons.
Run it with:
cd examples/package-minimal
npm install
npm run dev
Run the engine micro-benchmark:
npm run bench
It compares render-loop cost for:
This repo can publish the demo page to:
https://commandblock2.github.io/fluent-reveal-svelte/
CI workflow:
.github/workflows/deploy-pages.ymlmain and manual runsnpm run build:pages (Vite base path /fluent-reveal-svelte/)One-time GitHub setup:
Settings.Pages.Build and deployment source to GitHub Actions.After that, each push to main deploys the latest demo.
revealItem must be inside an ancestor using revealContainer.
If revealItem has border: true, it must also have an ancestor using revealBorder (usually a wrapper around the item).
Press scaling is applied to .reveal-press-content, not to the outer button node.
Wrap button content like this:
<button use:revealItem={{ border: true, hover: true, click: true }}>
<span class="reveal-press-content">
<span>Label</span>
</span>
</button>
The container will throw at runtime if required platform features are missing:
mask-compositeResizeObserverMutationObserver<script lang="ts">
import { revealBorder, revealContainer, revealItem, type RevealContainerOptions } from 'fluent-reveal-svelte'
import 'fluent-reveal-svelte/styles/reveal.css'
const options: RevealContainerOptions = {
border: {
radius: 96,
color: 'rgba(255, 178, 109, 0.94)',
widthPx: 1,
fadeStopPct: 72,
transitionMs: 180,
},
hover: {
color: 'rgba(115, 220, 255, 0.22)',
},
focus: {
enabled: true,
color: 'rgba(146, 220, 255, 0.76)',
widthPx: 2,
offsetPx: 3,
glowPx: 14,
zIndex: 12,
},
click: {
enabled: true,
color: 'rgba(255, 243, 213, 0.55)',
press: {
scale: 0.98,
transitionMs: 96,
},
ripple: {
enabled: true,
durationMs: 980,
sizePx: 24,
startScale: 0.2,
endScale: 3.2,
startOpacity: 0.52,
midOpacity: 0.2,
endOpacity: 0.18,
coreStrength: 74,
midStrength: 42,
},
},
cacheRects: true,
}
</script>
<section use:revealContainer={options}>
<div use:revealBorder>
<button
class="my-button"
use:revealItem={{ border: true, hover: true, click: true }}
>
<span class="reveal-press-content">
<span>Deploy</span>
<small>D P L</small>
</span>
</button>
</div>
</section>
<style>
.my-button {
border-radius: 12px;
border: 1px solid rgba(20, 35, 50, 0.16);
background: rgba(255, 255, 255, 0.35);
color: #15293a;
padding: 0.55rem 0.75rem;
display: flex;
width: 100%;
}
.my-button > .reveal-press-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.6rem;
width: 100%;
}
</style>
revealContainerAttach to a parent region.
type RevealContainerOptions = {
enabled?: boolean
border?: {
radius?: number
color?: string
widthPx?: number
fadeStopPct?: number
transitionMs?: number
}
hover?: {
color?: string
}
focus?: {
enabled?: boolean
color?: string
widthPx?: number
offsetPx?: number
glowPx?: number
zIndex?: number
}
click?: {
enabled?: boolean
color?: string
press?: {
scale?: number
transitionMs?: number
}
ripple?: {
enabled?: boolean
durationMs?: number
sizePx?: number
startScale?: number
endScale?: number
startOpacity?: number
midOpacity?: number
endOpacity?: number
coreStrength?: number
midStrength?: number
}
}
throttle?: 'raf'
cacheRects?: boolean
debug?: boolean
}
| Option | Range | Default |
|---|---|---|
border.radius |
0..600 |
90 |
border.widthPx |
0..16 |
1 |
border.fadeStopPct |
10..100 |
72 |
border.transitionMs |
0..2000 |
180 |
focus.widthPx |
0..16 |
2 |
focus.offsetPx |
0..24 |
3 |
focus.glowPx |
0..64 |
14 |
focus.zIndex |
0..1000 |
12 |
click.press.scale |
0.8..1 |
0.98 |
click.press.transitionMs |
0..1000 |
96 |
click.ripple.durationMs |
120..4000 |
980 |
click.ripple.sizePx |
4..240 |
24 |
click.ripple.startScale |
0.05..2.5 |
0.2 |
click.ripple.endScale |
0.2..8 |
3.2 |
click.ripple.startOpacity |
0..1 |
0.52 |
click.ripple.midOpacity |
0..1 |
0.2 |
click.ripple.endOpacity |
0..1 |
0.18 |
click.ripple.coreStrength |
0..100 |
74 |
click.ripple.midStrength |
0..100 |
42 |
Notes:
throttle currently supports only 'raf'id values are immutable once registered (revealBorder / revealItem)revealBorderAttach to a wrapper element that should render the border effect.
type RevealBorderOptions = {
id?: string
}
revealItemAttach to interactive elements.
type RevealItemOptions = {
id?: string
border: boolean
hover: boolean
click: boolean
}
Default behavior when options are omitted:
border: falsehover: trueclick: trueEffects are composited overlays. Visual quality depends on your surface styling.
Recommended:
rgba(..., 0.2..0.7)) for richer hover/ripple blending.If revealItem uses border: true, yes, use an ancestor wrapper with use:revealBorder.revealItem searches parent elements for a border host and throws if none is found.
Wrap content in .reveal-press-content if you want press scaling.
This keeps the outer button and border stable while content scales.
A lot for visual quality, not for correctness.
Opaque surfaces still work functionally, but reveal layers are less visible.
Yes, see the Minimal Example section above.
[fluent-reveal] revealItem requires an ancestor with use:revealContainer.use:revealContainer on a parent.[fluent-reveal] revealItem "<id>" has border=true but no registered revealBorder host was found.use:revealBorder ancestor.GPL-3.0 (see LICENSE).
Third-party asset notices are listed in THIRD_PARTY_NOTICES.md.