Littkk ·

Hide and show elements on scroll — works on multiple elements, in any direction, with no classes, no config files, and no framework adapters.
Just add a data-scroll-top (or bottom / left / right) attribute and call littkk()!
There are two modes, determined by whether the attribute has a value.
Slides the element out of the viewport on scroll down, back in on scroll up. Element must be position: fixed | sticky | absolute.
<header data-scroll-top></header>
<footer data-scroll-bottom></footer>
<nav data-scroll-left></nav>
<aside data-scroll-right></aside>| Direction | Attribute | Behavior |
|---|---|---|
| Up | data-scroll-top |
slides up out of viewport |
| Down | data-scroll-bottom |
slides down out of viewport |
| Left | data-scroll-left |
slides left out of viewport |
| Right | data-scroll-right |
slides right out of viewport |
The element is never hidden. Instead its CSS edge property (top / bottom / left / right) transitions to the given value on scroll down, and reverts on scroll up.
Bare numbers are treated as px. Any CSS unit is accepted.
<!-- top → 0px when scrolled down, reverts when scrolled up -->
<div data-scroll-top="0"></div>
<!-- top → 1rem -->
<div data-scroll-top="1rem"></div>
<!-- multiple axes on the same element -->
<div data-scroll-bottom="0" data-scroll-right="0"></div>| Attribute | Values | Default | Applies to |
|---|---|---|---|
data-duration |
ms | 300 |
both modes |
data-delay |
ms | 0 |
both modes |
data-offset |
px | auto from computed style | hide mode only |
data-delay — ms to wait after scroll stops before executing. Scrolling again resets the timer. Showing is always immediate.
data-offset — overrides the auto-computed slide distance. Useful when getComputedStyle().top/bottom/left/right is unreliable, e.g. with inset shorthand or a pre-existing transform.
littkk({
scrollTarget: "#my-div", // window (default), HTMLElement, or CSS selector
threshold: 5, // min px delta before triggering. Default: 5
showAtTop: true, // force-show all elements when scroll position reaches 0. Default: true
});export interface LittkkController {
/** Re-scan DOM and sync new elements to current scroll state. */
refresh: () => void;
/** Remove scroll listener and reset all element styles. */
destroy: () => void;
/** Set enable or disable */
setEnable: (enable: boolean) => void;
}<header data-scroll-top style="position: fixed; top: 0; width: 100%;">
...
</header>
<nav data-scroll-left style="position: fixed; left: 0;">...</nav>
<footer data-scroll-bottom style="position: fixed; bottom: 0; width: 100%;">
...
</footer>
<script type="module">
import { littkk } from "littkk";
littkk();
</script>import { useEffect } from "react";
import { littkk, LittkkOptions } from "littkk";
function useLittkk(options?: LittkkOptions) {
useEffect(() => {
const ctrl = littkk(options);
return () => ctrl.destroy();
}, []);
}export default function App() {
useLittkk();
return (
<>
<header
data-scroll-top
style={{ position: "fixed", top: 0, width: "100%" }}>
...
</header>
{/* Shifts top to 0 when header hides, reverts when header shows */}
<main data-scroll-top="0" style={{ position: "fixed", top: 64 }}>
...
</main>
</>
);
}For a scrollable container, pass the ref as scrollTarget. Call ctrl.refresh() after conditionally rendered elements mount.
export default function Feed() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const ctrl = littkk({ scrollTarget: containerRef.current });
return () => ctrl.destroy();
}, []);
return (
<div ref={containerRef} style={{ height: "100vh", overflowY: "auto" }}>
<header data-scroll-top style={{ position: "sticky", top: 0 }}>
...
</header>
</div>
);
}<script setup>
import { onUnmounted } from "vue";
import { littkk } from "littkk";
const ctrl = littkk();
onUnmounted(() => ctrl.destroy());
</script>
<template>
<header data-scroll-top style="position: fixed; top: 0; width: 100%;">
...
</header>
<main>...</main>
</template>For a scrollable container:
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { littkk } from "littkk";
const containerRef = ref(null);
let ctrl;
onMounted(() => {
ctrl = littkk({ scrollTarget: containerRef.value });
});
onUnmounted(() => ctrl?.destroy());
</script>
<template>
<div ref="containerRef" style="height: 100vh; overflow-y: auto;">
<header data-scroll-top style="position: sticky; top: 0;">...</header>
</div>
</template>Call ctrl.refresh() after conditionally rendered elements mount — e.g. in a watch or after an async operation.
- xior - A tiny but powerful fetch wrapper with plugins support and axios-like API
- tsdk - Type-safe API development CLI tool for TypeScript projects
- broad-infinite-list - ⚡ High performance and Bidirectional infinite scrolling list component for React and Vue3
Found an issue? Please feel free to create issue
If you find this project helpful, consider buying me a coffee.