A high-performance, infinite scrolling grid component for Svelte 5 that provides smooth touch/mouse interactions with momentum-based scrolling. Perfect for displaying large datasets in a grid format with custom cell renderers.
šŖ© Explore Thiings.co ā thiings.co
This is the component that powers the interactive grid on thiings.co - A growing collection of 1200+ free 3D icons, generated with AI.
š® Try the Live Demo ā npm run dev
Experience ThiingsGrid in action with interactive examples and copy-paste ready code.
This component is currently part of this repository. To use it in your project:
src/lib/ThiingsGrid.svelte
file to your projectnpm install svelte@^5.0.0
<script lang="ts">
import ThiingsGrid, { type ItemConfig } from '$lib/ThiingsGrid.svelte';
</script>
{#snippet myCell(itemConfig: ItemConfig)}
<div class="absolute inset-1 flex items-center justify-center">
{itemConfig.gridIndex}
</div>
{/snippet}
<div style="width: 100vw; height: 100vh;">
<ThiingsGrid
gridSize={80}
renderItem={myCell}
/>
</div>
Prop | Type | Required | Default | Description |
---|---|---|---|---|
gridSize |
number |
ā | - | Size of each grid cell in pixels |
renderItem |
Snippet<[ItemConfig]> |
ā | - | Svelte 5 snippet to render each grid cell |
class |
string |
ā | - | CSS class name for the container |
initialPosition |
Position |
ā | { x: 0, y: 0 } |
Initial scroll position |
The renderItem
snippet receives an ItemConfig
object with:
Property | Type | Description |
---|---|---|
gridIndex |
number |
Unique index for the grid cell |
position |
Position |
Grid coordinates { x: number, y: number } |
isMoving |
boolean |
Whether the grid is currently being moved/scrolled |
type Position = {
x: number;
y: number;
};
<script lang="ts">
import ThiingsGrid, { type ItemConfig } from "$lib/ThiingsGrid.svelte";
</script>
{#snippet simpleNumberCell(itemConfig: ItemConfig)}
<div class="absolute inset-1 flex items-center justify-center bg-blue-50 border border-blue-500 rounded text-sm font-bold text-blue-800">
{itemConfig.gridIndex}
</div>
{/snippet}
<ThiingsGrid
gridSize={80}
renderItem={simpleNumberCell}
initialPosition={{ x: 0, y: 0 }}
/>
<script lang="ts">
import ThiingsGrid, { type ItemConfig } from "$lib/ThiingsGrid.svelte";
</script>
{#snippet colorfulCell(itemConfig: ItemConfig)}
{@const colors = [
"bg-red-300",
"bg-green-300",
"bg-blue-300",
"bg-yellow-300",
"bg-pink-300",
"bg-cyan-300",
]}
{@const colorClass = colors[itemConfig.gridIndex % colors.length]}
<div
class="absolute inset-0 flex items-center justify-center {colorClass} text-xs font-bold text-gray-800 shadow-sm"
>
{itemConfig.gridIndex}
</div>
{/snippet}
<ThiingsGrid gridSize={100} renderItem={colorfulCell} />
<script lang="ts">
import ThiingsGrid, { type ItemConfig } from "$lib/ThiingsGrid.svelte";
</script>
{#snippet cardCell(itemConfig: ItemConfig)}
<div
class="absolute inset-1 flex flex-col items-center justify-center bg-white border border-gray-200 rounded-xl p-2 text-xs text-gray-800 transition-shadow {itemConfig.isMoving ? 'shadow-xl' : 'shadow-md'}"
>
<div class="text-base font-bold mb-1">#{itemConfig.gridIndex}</div>
<div class="text-[10px] text-gray-500">
{itemConfig.position.x}, {itemConfig.position.y}
</div>
</div>
{/snippet}
<ThiingsGrid
gridSize={150}
renderItem={cardCell}
/>
Always use absolute positioning within your snippet components for optimal performance:
<!-- ā
Good -->
{#snippet myCell(itemConfig: ItemConfig)}
<div class="absolute inset-1 ...">
{itemConfig.gridIndex}
</div>
{/snippet}
<!-- ā Avoid - can cause layout issues -->
{#snippet myCell(itemConfig: ItemConfig)}
<div class="w-full h-full ...">
{itemConfig.gridIndex}
</div>
{/snippet}
For better performance with complex cells, use Svelte 5's reactive patterns:
{#snippet optimizedCell(itemConfig: ItemConfig)}
{@const computedValue = expensiveCalculation(itemConfig.gridIndex)}
<div class="absolute inset-1 ...">
{computedValue}
</div>
{/snippet}
Ensure the ThiingsGrid has a defined container size:
<!-- ā
Good - explicit container size -->
<div style="width: 100vw; height: 100vh;">
<ThiingsGrid gridSize={80} renderItem={myCell} />
</div>
<!-- ā
Good - CSS classes with defined dimensions -->
<div class="w-screen h-screen">
<ThiingsGrid gridSize={80} renderItem={myCell} />
</div>
You can access the current grid position programmatically:
<script lang="ts">
import ThiingsGrid from "$lib/ThiingsGrid.svelte";
let gridComponent: ThiingsGrid;
const getCurrentPosition = () => {
if (gridComponent) {
const position = gridComponent.getCurrentPosition();
console.log('Current position:', position);
}
};
</script>
<ThiingsGrid
bind:this={gridComponent}
gridSize={80}
renderItem={myCell}
/>
<script lang="ts">
import { onMount } from 'svelte';
let gridSize = $state(80);
onMount(() => {
const handleResize = () => {
const width = window.innerWidth;
if (width < 768) {
gridSize = 60; // Smaller on mobile
} else if (width < 1024) {
gridSize = 80; // Medium on tablet
} else {
gridSize = 100; // Larger on desktop
}
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
});
</script>
<ThiingsGrid
gridSize={gridSize}
renderItem={myCell}
/>
The component handles:
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
src/
āāā lib/
ā āāā ThiingsGrid.svelte # Main component
ā āāā components/
ā āāā examples/ # Example implementations
ā ā āāā SimpleNumbers.svelte
ā ā āāā ColorfulGrid.svelte
ā ā āāā EmojiFun.svelte
ā ā āāā CardLayout.svelte
ā ā āāā ThiingsIcons.svelte
ā āāā Playground.svelte # Example viewer
ā āāā SourceCode.svelte # Source code display
ā āāā SourceCode.svelte.js # Source code data
ā āāā Sidebar.svelte # Example navigation
āāā routes/
āāā +page.svelte # Main demo application
This Svelte version maintains API compatibility with the original React version:
$state
, $derived
, $effect
) instead of React hooksbind:this
instead of useRef
$props()
rune// React version
const MyCell = ({ gridIndex, position, isMoving }) => (
<div className="absolute inset-1">
{gridIndex}
</div>
);
<ThiingsGrid gridSize={80} renderItem={MyCell} />
<!-- Svelte version -->
{#snippet myCell({ gridIndex, position, isMoving })}
<div class="absolute inset-1">
{gridIndex}
</div>
{/snippet}
<ThiingsGrid gridSize={80} renderItem={myCell} />
git checkout -b feature/amazing-feature
)git commit -m 'Add some amazing feature'
)git push origin feature/amazing-feature
)MIT License - see the LICENSE file for details.
npm run dev
to see all examples