A collection of 50+ Svelte 5 runes-first utility composables.
No stores. No external dependencies. SSR-safe. Fully typed.
| โก Svelte 5 Runes | Built for $state, $derived, $effect โ no stores |
| ๐ฒ Tree-shakable | Import only what you use |
| ๐ Fully typed | First-class TypeScript, no any |
| ๐ SSR safe | All browser APIs are guarded |
| ๐ฆ Zero deps | No runtime dependencies |
| ๐งน Auto cleanup | Listeners removed on component destroy |
npm install @ariefsn/svelte-use
# or
pnpm add @ariefsn/svelte-use
# or
bun add @ariefsn/svelte-use
Requires Svelte 5 as a peer dependency.
| Composable | Description |
|---|---|
useAnimate |
Reactive Web Animations API wrapper |
useParallax |
Parallax effect based on pointer or device tilt |
useTransition |
Animated numeric transitions with easing |
| Composable | Description |
|---|---|
useFetch |
Reactive fetch with loading/error state |
useWebSocket |
Reactive WebSocket with auto-reconnect |
| Composable | Description |
|---|---|
useInterval |
Reactive interval counter |
useIntervalFn |
Run a callback on an interval |
useNow |
Reactive current Date |
useTimeout |
Reactive timeout flag |
useTimeoutFn |
Run a callback after a delay |
useTimeoutPoll |
Poll a callback with timeout-based intervals |
useTimestamp |
Reactive current timestamp (ms) |
| Composable | Description |
|---|---|
useToggle |
Reactive boolean toggle |
useCounter |
Reactive counter with inc/dec/reset |
usePrevious |
Track previous value of any reactive getter |
useSorted |
Reactive sorted copy of an array |
useCycleList |
Cycle through a list reactively |
useCountdown |
Countdown timer with start/stop/reset |
useTimeAgo |
Human-readable relative time string |
| Composable | Description |
|---|---|
useDebounce |
Debounce any reactive getter |
| Composable | Description |
|---|---|
useMagicKeys |
Reactive keyboard state via Proxy โ single keys or combos |
useKeyModifier |
Track Ctrl/Shift/Alt/Meta state |
useScroll |
Scroll position, direction, edge arrival |
useScrollLock |
Lock/unlock body scroll |
| Composable | Description |
|---|---|
useMouse |
Viewport-relative pointer position |
useMousePressed |
Detect mouse button press state |
useDraggable |
Full-featured draggable with axis, bounds, handles |
| Composable | Description |
|---|---|
useElementSize |
Reactive element dimensions via ResizeObserver |
useIntersectionObserver |
Visibility detection via IntersectionObserver |
useResizeObserver |
Raw ResizeObserver with callback |
useMutationObserver |
DOM mutation observation |
| Composable | Description |
|---|---|
useIdle |
Detect user idle state |
useNetwork |
Network Information API (downlink, RTT, effectiveType) |
useGeolocation |
Reactive geolocation via watchPosition |
useBreakpoints |
Reactive responsive breakpoints |
useBrowserLocation |
Reactive browser location (URL, hash, search) |
useNavigatorLanguage |
Reactive navigator language |
useOnline |
Reactive online/offline status |
usePageLeave |
Detect when user leaves the page |
| Composable | Description |
|---|---|
useLocalStorage |
Reactive localStorage with SSR safety |
useIndexedDB |
Reactive IndexedDB with CRUD & querying |
useBase64 |
Reactive Base64 encode/decode |
useObjectUrl |
Reactive object URL from Blob/File |
useSessionStorage |
Reactive sessionStorage with SSR safety |
| Composable | Description |
|---|---|
useClickOutside |
Detect clicks outside an element |
useDropZone |
Drag-and-drop zone with file/data support |
useElementHover |
Detect hover state of an element |
useFocus |
Reactive focus state of an element |
| Composable | Description |
|---|---|
useFps |
Reactive frames-per-second counter |
useThrottleFn |
Throttle any function |
useDebounceFn |
Debounce any function |
| Composable | Description |
|---|---|
useVirtualList |
Efficient virtual list rendering |
| Composable | Description |
|---|---|
useClipboard |
Reactive clipboard read/write |
useBattery |
Reactive Battery Status API |
useSpeechRecognition |
Reactive Web Speech Recognition API |
useToggleReactive boolean toggle.
import { useToggle } from '@ariefsn/svelte-use';
const { value, toggle, set } = useToggle();
// value โ false
toggle(); // value โ true
set(false); // value โ false
| Return | Type | Description |
|---|---|---|
value |
boolean (reactive) |
Current boolean state |
toggle |
() => void |
Flips the value |
set |
(v: boolean) => void |
Sets the value explicitly |
useCounterReactive counter with increment, decrement, and reset.
import { useCounter } from '@ariefsn/svelte-use';
const { value, inc, dec, reset } = useCounter(0);
inc(); // value โ 1
inc(5); // value โ 6
dec(3); // value โ 3
reset(); // value โ 0
| Return | Type | Description |
|---|---|---|
value |
number (reactive) |
Current counter value |
inc |
(delta?: number) => void |
Increment by delta (default 1) |
dec |
(delta?: number) => void |
Decrement by delta (default 1) |
reset |
() => void |
Reset to initial value |
usePreviousTracks the previous value of any reactive getter. Returns undefined until the tracked value changes for the first time.
import { usePrevious } from '@ariefsn/svelte-use';
let count = $state(0);
const prev = usePrevious(() => count);
// prev() โ undefined
count = 1;
// prev() โ 0
count = 2;
// prev() โ 1
| Parameter | Type | Description |
|---|---|---|
getter |
() => T |
Reactive getter function to observe |
Returns a () => T | undefined getter.
useDebounceDelays a reactive value until the source stops changing for the specified duration.
import { useDebounce } from '@ariefsn/svelte-use';
let query = $state('');
const debounced = useDebounce(() => query, 500);
// debounced() updates only after 500 ms of inactivity
| Parameter | Type | Default | Description |
|---|---|---|---|
getter |
() => T |
โ | Reactive getter to debounce |
delay |
number |
300 |
Delay in milliseconds |
Returns a () => T getter.
useLocalStorageReactive localStorage with SSR safety. Values are serialised with JSON.stringify / JSON.parse. Falls back to initial in non-browser environments or on parse errors.
import { useLocalStorage } from '@ariefsn/svelte-use';
const theme = useLocalStorage<'light' | 'dark'>('theme', 'light');
theme.set('dark'); // persists to localStorage
theme.value; // 'dark'
| Parameter | Type | Description |
|---|---|---|
key |
string |
localStorage key |
initial |
T |
Fallback when key is absent or in SSR |
| Return | Type | Description |
|---|---|---|
value |
T (reactive) |
Current stored value |
set |
(v: T) => void |
Update and persist the value |
useIndexedDBReactive IndexedDB utility with full CRUD, querying, and filtering. SSR-safe โ all operations are no-ops on the server. Values survive page refreshes and browser restarts.
import { useIndexedDB } from '@ariefsn/svelte-use';
interface Note {
id?: number;
text: string;
done: boolean;
}
const db = useIndexedDB<Note>('my-app', 'notes');
// CRUD
await db.add({ text: 'Buy milk', done: false }); // returns generated key
await db.get(1); // Note | undefined
await db.getAll(); // Note[]
await db.update({ id: 1, text: 'Buy milk', done: true });
await db.remove(1);
await db.clear(); // delete all records
// Reactive state (updated automatically after every mutation)
db.items; // Note[] โ all records
db.loading; // boolean
db.error; // Error | null
// Filtering
const pending = await db.query((n) => !n.done); // Note[]
Options
| Parameter | Type | Default | Description |
|---|---|---|---|
dbName |
string |
โ | IndexedDB database name |
storeName |
string |
โ | Object store name |
options.version |
number |
1 |
Schema version (increment to migrate) |
options.keyPath |
string |
'id' |
Primary key field name |
options.autoIncrement |
boolean |
true |
Auto-generate numeric keys |
Returns
| Property / Method | Type | Description |
|---|---|---|
items |
T[] (reactive) |
All stored records; refreshed after every mutation |
loading |
boolean (reactive) |
true while an async operation is in flight |
error |
Error | null (reactive) |
Last error, or null |
add(record) |
Promise<IDBValidKey | undefined> |
Insert record; returns generated key |
get(key) |
Promise<T | undefined> |
Fetch single record by primary key |
getAll() |
Promise<T[]> |
Fetch all records and sync items |
update(record) |
Promise<void> |
Replace record (must include key field) |
remove(key) |
Promise<void> |
Delete record by primary key |
query(filter) |
Promise<T[]> |
Return records matching a predicate (doesn't modify items) |
clear() |
Promise<void> |
Delete all records |
useSortedReturns a reactive sorted copy of an array. Never mutates the source.
import { useSorted } from '@ariefsn/svelte-use';
let items = $state([3, 1, 4, 1, 5]);
const sorted = useSorted(() => items);
// sorted() โ [1, 1, 3, 4, 5]
const desc = useSorted(
() => items,
(a, b) => b - a
);
useCycleListCycle through a list reactively. Wraps around at both ends.
import { useCycleList } from '@ariefsn/svelte-use';
const cycle = useCycleList(['light', 'dark', 'system']);
cycle.state(); // โ 'light'
cycle.next(); // โ 'dark'
cycle.prev(); // โ 'light'
cycle.setIndex(2);
useCountdownCountdown timer with start/stop/reset.
import { useCountdown } from '@ariefsn/svelte-use';
const timer = useCountdown(60); // 60s at 1s intervals
timer.start();
timer.count(); // โ 60, 59, โฆ, 0
timer.isActive(); // โ true
timer.stop();
timer.reset();
useTimeAgoReactive human-readable relative time string.
import { useTimeAgo } from '@ariefsn/svelte-use';
const ago = useTimeAgo(() => new Date('2024-01-01'));
ago(); // โ "1 year ago"
useMagicKeysTrack any key or combination via a Proxy.
import { useMagicKeys } from '@ariefsn/svelte-use';
const keys = useMagicKeys();
keys['ctrl+s'](); // โ true while Ctrl+S is held
keys['shift'](); // โ true while Shift is held
useKeyModifierTrack a specific modifier key state.
import { useKeyModifier } from '@ariefsn/svelte-use';
const ctrl = useKeyModifier('ctrl'); // 'ctrl' | 'shift' | 'alt' | 'meta'
ctrl(); // โ true while Ctrl is held
useScrollScroll position, direction flags, edge detection for any scrollable element or window.
import { useScroll } from '@ariefsn/svelte-use';
const scroll = useScroll(); // window
const elScroll = useScroll(() => myEl, { offset: { bottom: 20 } });
scroll.y(); // โ number
scroll.isScrolling(); // โ boolean
scroll.arrivedState.bottom(); // โ boolean
scroll.directions.down(); // โ boolean
scroll.scrollTo({ top: 0 });
useMouseTracks viewport-relative pointer position.
import { useMouse } from '@ariefsn/svelte-use';
const mouse = useMouse();
mouse.x(); // โ number
mouse.y(); // โ number
mouse.sourceType(); // โ 'mouse' | 'touch' | null
useMousePressedDetects whether any mouse button is held.
import { useMousePressed } from '@ariefsn/svelte-use';
const pressed = useMousePressed();
pressed(); // โ boolean
useDraggableFull-featured draggable with axis constraints, bounds, handles, and callbacks.
import { useDraggable } from '@ariefsn/svelte-use';
const drag = useDraggable(() => el, {
axis: 'x',
initialValue: { x: 100, y: 100 },
onEnd: (pos) => console.log(pos)
});
drag.x(); // โ number
drag.isDragging(); // โ boolean
drag.style(); // โ "transform: translate(100px, 0px);"
useElementSizeReactively tracks element dimensions via ResizeObserver.
import { useElementSize } from '@ariefsn/svelte-use';
const size = useElementSize(() => el);
size.width(); // โ number
size.height(); // โ number
useIntersectionObserverViewport visibility detection.
import { useIntersectionObserver } from '@ariefsn/svelte-use';
const { isIntersecting, stop } = useIntersectionObserver(() => el, { threshold: 0.5 });
isIntersecting(); // โ boolean
useResizeObserverRaw ResizeObserver wrapper with automatic cleanup.
import { useResizeObserver } from '@ariefsn/svelte-use';
const { stop } = useResizeObserver(
() => el,
(entry) => {
console.log(entry.contentRect.width);
}
);
useMutationObserverObserves DOM mutations on any node.
import { useMutationObserver } from '@ariefsn/svelte-use';
const { stop } = useMutationObserver(
() => el,
(mutations) => {
for (const m of mutations) console.log(m.type);
},
{ childList: true, subtree: true }
);
useIdleDetect user inactivity.
import { useIdle } from '@ariefsn/svelte-use';
const { isIdle, reset } = useIdle(5000); // idle after 5s
isIdle(); // โ boolean
useNetworkReactive Network Information API.
import { useNetwork } from '@ariefsn/svelte-use';
const net = useNetwork();
net.effectiveType(); // โ '4g' | '3g' | undefined
net.downlink(); // โ Mbps | undefined
net.saveData(); // โ boolean | undefined
useGeolocationReactive geolocation via watchPosition.
import { useGeolocation } from '@ariefsn/svelte-use';
const geo = useGeolocation({ enableHighAccuracy: true });
geo.coords()?.latitude; // โ number | null
geo.error(); // โ GeolocationPositionError | null
geo.isSupported(); // โ boolean
useFetchReactive fetch wrapper with loading/error state.
import { useFetch } from '@ariefsn/svelte-use';
const { data, loading, error, execute } = useFetch<User[]>('/api/users');
// data() โ User[] | null
// loading() โ boolean
// error() โ Error | null
useWebSocketReactive WebSocket with auto-reconnect.
import { useWebSocket } from '@ariefsn/svelte-use';
const ws = useWebSocket('wss://echo.example.com');
ws.send('hello');
ws.data(); // โ last received message
ws.status(); // โ 'OPEN' | 'CLOSED' | 'CONNECTING'
useAnimateReactive Web Animations API wrapper.
import { useAnimate } from '@ariefsn/svelte-use';
const { animate, stop } = useAnimate(() => el);
animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300 });
useParallaxParallax effect based on pointer position or device tilt.
import { useParallax } from '@ariefsn/svelte-use';
const { tilt, roll } = useParallax(() => containerEl);
// tilt() โ number (โ0.5 to 0.5)
// roll() โ number (โ0.5 to 0.5)
useTransitionAnimated numeric transitions with easing.
import { useTransition } from '@ariefsn/svelte-use';
let target = $state(0);
const animated = useTransition(() => target, { duration: 500 });
// animated() smoothly interpolates to target
useIntervalReactive interval counter.
import { useInterval } from '@ariefsn/svelte-use';
const { counter, pause, resume } = useInterval(1000);
counter(); // โ increments every second
useNowReactive current Date.
import { useNow } from '@ariefsn/svelte-use';
const now = useNow();
now(); // โ Date (updated every second by default)
useTimestampReactive current timestamp in milliseconds.
import { useTimestamp } from '@ariefsn/svelte-use';
const ts = useTimestamp();
ts(); // โ number (ms since epoch)
useBreakpointsReactive responsive breakpoints.
import { useBreakpoints } from '@ariefsn/svelte-use';
const bp = useBreakpoints({ sm: 640, md: 768, lg: 1024 });
bp.lg(); // โ true if viewport โฅ 1024px
bp.between('sm', 'lg')(); // โ boolean
useClipboardReactive clipboard read/write.
import { useClipboard } from '@ariefsn/svelte-use';
const { text, copy, copied } = useClipboard();
copy('Hello!');
copied(); // โ true for 1.5s after copy
useBatteryReactive Battery Status API.
import { useBattery } from '@ariefsn/svelte-use';
const battery = useBattery();
battery.level(); // โ 0โ1
battery.charging(); // โ boolean
useVirtualListEfficient virtual list rendering for large datasets.
import { useVirtualList } from '@ariefsn/svelte-use';
const { list, containerProps, wrapperProps } = useVirtualList(items, { itemHeight: 40 });
// list() โ only the visible slice of items
bun install
bun run dev
To run tests:
bun run test
# build the library
bun run build
# publish to npm
npm publish --access public