Skip to content
ColorKit

Free spritesheet generator

Spritesheet generator with a real packer

Free and without a signup: select up to 200 images and a MaxRects packer fits them into a single PNG, then hands back the CSS, a JSON atlas and — for a one-row strip — the steps() animation that plays them. Because the sprites are placed rather than gridded, a set whose icons differ in size occupies 82 to 94% of the canvas, where a uniform grid of the same images lands nearer 30 to 45% once the tallest sprite has set every row height. Spacing and per-sprite edge bleed are adjustable from 0, which is what stops a neighboring frame leaking in when the sheet is scaled by 1.25 or sampled by a GPU.

  • 100% free
  • No signup
  • Up to 200 images
  • MaxRects packing
  • CSS + JSON atlas

Select the whole folder at once — PNG, WebP, GIF, JPEG or SVG, up to 200 files of 10 MB each. Flat icons at 16 to 128 px are what a sheet is for; photographs are not.

Press Ctrl+V anywhere on the page to drop in one more sprite copied from a design tool — no need to click the field first.

How to build a spritesheet

Three decisions: which images, which packer, and whether the output is CSS or an atlas.

  1. Select every image that belongs on the sheet

    Multi-select the whole icon folder in one go — PNG, WebP, GIF, JPEG and SVG are all decoded, up to 200 files of 10 MB each. An SVG needs width and height attributes to have an intrinsic pixel size; without them the browser decodes it to 0×0 and the file is listed as skipped rather than silently dropped. Frames are ordered by filename with digits compared as numbers, so frame-9 lands before frame-10 instead of after frame-1.

  2. Choose the packer, then the spacing and bleed

    MaxRects with best-short-side-fit is the default and is worth keeping whenever the sprites differ in size; the shelf packer is shown beside it with its own occupancy figure so the difference is a number rather than a claim. Single-row mode gives up packing density on purpose, because a steps() animation needs identical cells in one row. Two pixels of spacing and one pixel of bleed suit a set of flat icons: spacing keeps the neighbor out, bleed repeats each sprite's own edge into the gap so a fractional scale factor has something of the right color to sample.

  3. Save the PNG, then copy the CSS or the JSON atlas

    The sheet downloads as spritesheet.png and the CSS references it by that filename, so rename it in both places or keep them together. The CSS is one .sprite base rule plus one rule per frame carrying width, height and background-position; the JSON tab writes the same geometry as a TexturePacker hash that Phaser, PixiJS and most engine loaders read directly. If your sources are @2x art, switch the density selector before copying — the bitmap is untouched and every length in the CSS is halved instead.

Technical specifications

Packing algorithmsMaxRects with the best-short-side-fit heuristic, and a shelf packer using next-fit decreasing height. Both run on every change so the occupancy of the one you did not pick is shown beside the one you did
Occupancy figureArtwork area divided by canvas area, as a percentage. Spacing, bleed and any power-of-two rounding count against it, which is the point of measuring it
Sheet sizingWidth is the square root of the total padded area, rounded up and clamped to a 512, 1024, 2048 or 4096 px ceiling. Height is binary-searched between the shelf result and the area-over-width floor, roughly ten packing passes, then cropped to the last occupied row. Both sides round up to a power of two on request
RotationNever. background-position translates the image and nothing else, so an axis-aligned frame is the only kind a stylesheet can address; the JSON atlas records rotated: false for every entry
Spacing and bleed0 to 32 px of gap between frames, 0 to 8 px of edge extrusion drawn into that gap from the sprite's own outermost row and column
Ceilings200 images, 10 MB per file, 32,767 px on a canvas side (Firefox's limit) and 16,777,216 px of canvas area (Safari's). Exceeding one names the constraint rather than freezing the tab
OutputsOne RGBA PNG from canvas.toBlob, a CSS block of one base rule plus one rule per frame, and a TexturePacker JSON hash with frame, spriteSourceSize, sourceSize and a meta block. Single-row mode adds @keyframes with steps() at 1 to 60 fps
Retina handlingA 1x, 2x or 3x density selector divides every CSS length including background-size. The bitmap is never resampled — a 2x sheet stays at full resolution and the stylesheet halves the numbers

Frequently asked questions

Is a spritesheet still worth it now that everything is HTTP/2?

For icons on a normal website, usually not — and that is the honest answer this page leads with. The 2004 argument was request count: HTTP/1.1 allowed about six parallel connections per host, so 40 icons meant seven rounds of queuing. HTTP/2 multiplexes all 40 over one connection and the queue disappears. What survives the change is narrower: a canvas or WebGL renderer batches draw calls when every quad samples one texture, an animation frame sequence has to be one image to be driven by steps(), and a single sheet decodes once instead of 40 times, which matters more than transfer on a low-end phone. If your case is a set of interface icons, an inline SVG sprite is the better tool.

A thin sliver of the neighboring sprite shows along one edge. Why?

The sheet is being sampled at a fractional scale, so the pixel at the frame boundary is a blend of your sprite and whatever sits next to it. It happens on a 1.25 or 1.5 device pixel ratio, inside a CSS transform, and in any WebGL texture with linear filtering. Spacing alone moves the neighbor out of reach but replaces it with transparency, which fringes the edge dark once alpha is premultiplied. The fix that works is bleed: this page repeats each sprite's own edge row and column outward into the gap, so the half-pixel the sampler steals is the sprite's own color.

My steps() animation flashes a blank frame or skips the first one.

The to-position must be the full sheet width, not the width minus one frame. With steps(n) the browser holds n discrete values between the from and to positions and never renders the to position itself, so -(n × frameWidth) yields exactly frames 0 through n-1. Subtracting one frame first, which reads more intuitively, plays the last frame twice and drops the first. The animation this page emits uses the full width, and the live preview steps through the same stride so the two cannot disagree.

Can the packer rotate sprites 90° to squeeze more in?

No, and not because it would be hard. Every packer written for game engines rotates, and the atlas format records rotated: true so the renderer can undo it in the shader. CSS has no equivalent: background-position can only translate the image, so a rotated frame would be unusable from a stylesheet. Rotation would buy a few percent of occupancy in exchange for output half this page's audience could not use, so every frame here is axis-aligned and the JSON records rotated: false.

The sheet came out larger than my source files added together.

That means the originals were compressing better separately than they do together, which is the standard result for photographs and for PNGs that were each quantized to their own small palette. A 32-color icon saved on its own carries a 32-entry palette at roughly 1 byte per pixel; merge 40 of them and the combined image needs true color, so every pixel costs 4 bytes before deflate sees it. The stats line above the preview compares the two totals for exactly this reason — when the sheet is bigger, the set is not worth sheeting.

Do the sheet dimensions have to be a power of two?

Not for CSS, and not for WebGL 2 or any modern GPU. The power-of-two checkbox exists for WebGL 1 and OpenGL ES 2, where a non-power-of-two texture cannot use mipmaps or repeat wrapping, and for a handful of older mobile drivers that quietly fall back to a slower path. Turning it on rounds both sides up to the next power of two, and a sheet that came out 565×632 becomes 1024×1024 — the same artwork at 30% occupancy instead of 89%. Leave it off unless a renderer is actually asking for it.

What is the JSON atlas for when I already have the CSS?

The JSON is for renderers that draw sprites themselves rather than through a stylesheet — Phaser, PixiJS, a plain canvas loop, or your own WebGL quad batcher. It is the TexturePacker hash layout: each frame keyed by original filename, with frame, spriteSourceSize and sourceSize rectangles, plus a meta block naming the image and the scale. The frame rectangles are the identical numbers the CSS turns into negative offsets, so the two outputs can never drift apart.

About sprite packing and when a sheet earns its place

Two unrelated traditions meet in the word spritesheet. Games have packed frames into one texture since hardware sprites existed, because a GPU batches draw calls only while every quad samples the same texture, and swapping textures mid-frame is the expensive part. The web borrowed the idea in March 2004, when Dave Shea named CSS sprites in A List Apart, and the motivation there was request count: HTTP/1.1 gave a browser about six parallel connections per host, so 40 separate icons queued in seven rounds. HTTP/2 multiplexes those 40 files over one connection and that argument is largely gone. What is left is the GPU case, the decode cost of 40 images on a slow phone, and animation frames, which have to share one bitmap for a steps() rule to walk through them. If your set is interface icons, an inline SVG sprite — a <symbol> block plus <use> references — beats a raster sheet on every axis: it scales, it recolors from currentColor, and it needs no position arithmetic. Run those through the SVG optimizer instead, or trace the raster originals first with PNG to SVG.

Where a sheet is the right answer, the packing is where tools differ. A uniform grid spends the height of the tallest sprite on every row and the width of the widest on every column, so a set mixing 16 px glyphs with a 96 px illustration wastes most of the canvas. A shelf packer sorts tall to short and fills rows, which recovers a lot for very little code. MaxRects keeps an explicit list of free rectangles, splits them on each placement and drops the ones swallowed by another, then picks the position leaving the smallest leftover on its shorter axis — the best-short-side-fit rule from Jukka Jylänki’s 2010 survey of bin-packing heuristics, which remains the practical benchmark. Because one MaxRects pass needs a sheet height decided in advance, this page binary-searches the height between the shelf result and the theoretical floor of area over width, which is worth another 3 to 8 points and costs about 40 ms for 200 sprites. Both packers run on every change and both occupancy figures are printed, so the comparison is a measurement rather than a claim. One thing it will not do is rotate: atlas packers routinely turn sprites 90° and record it, but background-position can only translate, so a rotated frame would be dead weight in a stylesheet.

The two details that break sheets in production are both about edges. First, scaling: at a 1.25 device pixel ratio, or inside a transform, or under GPU linear filtering, the sampler reads half a pixel past the frame it asked for. Spacing keeps the neighbor out of that half-pixel, but leaves transparency there, and transparency fringes premultiplied artwork dark — which is why this page extrudes the sprite’s own edge row and column into the gap rather than relying on spacing alone. Second, density: a @2x sheet is not a bigger sheet, it is the same sheet whose CSS lengths are all halved, background-size included. Omitting background-size is the single most common retina sprite bug, because every offset then lands at double its intended position and the sheet looks like it decoded wrong. The same square-artwork discipline shows up next door in the favicon generator and the ICO converter, which pack sizes into one container for the same reason; if you need a placeholder frame to test a sheet with, the avatar generator produces deterministic ones, and SVG to PNG rasterizes vector sources at the exact pixel size you want packed.

Where the images you pack are read

Every number on this page is worked out by JavaScript running in the tab you are reading it in. Nothing you type, paste or open is uploaded, logged or kept, which is also why the tools carry on working after you disconnect from the network.

The sheet is composited by a canvas in this tab and handed to you as an object URL, which is why the preview appears before any download and why the whole thing works with the network off. Closing the tab releases every image; nothing is stored between visits.