sanity-svelte-image
v0.0.4
Published
A Svelte component for rendering optimized Sanity images with automatic URL building and responsive support
Maintainers
Readme
sanity-svelte-image
A well-considered Svelte component for displaying images from Sanity. At a glance:
- Outputs a single
<img>tag, no nested DOM structure to mess with - Zero styling included so you can style it however you want…it's just an
imgtag! - Generates a
srcSetautomatically based on thewidthyou specify - Dynamic
srcSetfactor based on image output width - Knows exactly what size the image will be and sets
widthandheightattributes accordingly - Supports
cropandhotspotvalues from the Sanity Studio - Automatically crops to the most "interesting" part of the image if the aspect
ratio changes and no
hotspotis provided - Images are never scaled up
- Tiny bundle size with minimal dependencies
- TypeScript support
- Works with SvelteKit and any other Svelte-based framework
Note: Low-quality image preview support is not yet implemented but is planned for a future release.
Quick Start
Install it:
npm install sanity-svelte-image
# or
yarn add sanity-svelte-image
# or
pnpm add sanity-svelte-imageUse it:
You can find the full writeup on getting going below, but in the interest of making it easy to see if this is the thing you are looking for, here's a quick example of most of what you'll need to know:
Simplest Case:
This will render the image out assuming it will be displayed at half its original width with a srcSet included (multiplies vary based on original image size):
<script lang="ts">
import { Image } from 'sanity-svelte-image';
let { image } = $props();
</script>
<Image
{image}
alt="Demo image"
/>More full-featured example:
<script lang="ts">
import { Image } from 'sanity-svelte-image';
let { image, heroImage, thumbnailImage } = $props();
</script>
<!-- Hero image with priority loading -->
<Image
image={heroImage}
width={1200}
height={600}
mode="cover"
priority={true}
alt="Hero image"
class="w-full h-auto"
/>
<!-- Regular image with custom sizing -->
<Image
{image}
width={500}
height={250}
mode="cover"
queryParams={{ sharpen: 30, q: 80 }}
alt="Featured content"
class="rounded-lg shadow-md"
sizes="(min-width: 500px) 500px, 100vw"
/>
<!-- Natural size image (preserves original dimensions) -->
<Image
image={thumbnailImage}
natural={true}
alt="Thumbnail at natural size"
class="max-w-xs"
/>That's the gist. Read on for more. 👇
Details
How it works at a glance:
- The image asset reference is parsed to determine the source image dimensions and format
- SVG images get special treatment from the Sanity Image API (they don't support params), so they're handled differently
- All other images have
srcandsrcSetprops generated based on thewidthandheightprops you pass in (or the image dimensions if you don't pass in a width or height) - The
srcSetwidths depend on the size of the output image and the original image; there's logic to avoid wasteful tiny images or giant jumps in size between large entries - Values in the
srcSetare never duplicated and never upscale the image - Since we can compute the output dimensions of the image in all cases, the
widthandheightattributes are set automatically to avoid layout shifts - A few image params are applied by default:
auto=format- Sanity will use AVIF images if they're supported by the browser (note: if you specifyfmmanually, this won't be set)fit- if the image aspect ratio isn't changed, this will be set tomax; if the aspect ratio will change it's set tocropq- the quality is set to 75 by default, but you can override it with thequeryParamsprop
- The
loadingattribute will be set tolazyif it isn't supplied; useloading="eager"for images above the fold or setpriority={true} - The
altattribute will be set to an empty string if it isn't supplied; set it if it isn't a decorative image! - Query params passed to Sanity are all sorted and minimized for improved caching and smaller URLs
Props
This component accepts all standard HTML img attributes plus the following Sanity-specific props:
Required Props
image(SanityImageAsset) — Required - The Sanity image object containing the asset reference and optional crop/hotspot data
Optional Props
mode("cover" | "contain") — Optional - Usecoverto crop the image to match the requested aspect ratio (based onwidthandheight). Usecontainto fit the image to the boundaries provided without altering the aspect ratio. Defaults to"contain"unlessnatural={true}is set.width(number) — Optional - The target width of the image in pixels. Only used for determining the dimensions of the generated assets, not for layout. Use CSS to specify how the browser should render the image instead.height(number) — Optional - The target height of the image in pixels. Only used for determining the dimensions of the generated assets, not for layout. Use CSS to specify how the browser should render the image instead.natural(boolean) — Optional - When true, uses the original image dimensions and sets mode to "contain". Defaults tofalse.priority(boolean) — Optional - When true, setsloading="eager",fetchpriority="high", anddecoding="sync"for above-the-fold images. Defaults tofalse.baseUrl(string) — Optional - Custom base URL for the Sanity CDN. If not provided, uses environment variablesPUBLIC_SANITY_PROJECT_IDandPUBLIC_SANITY_DATASET.queryParams(object) — Optional - An object of query parameters to pass to the Sanity Image API. See the Sanity Image API documentation for a list of available options.
HTML Override Props
Since some props conflict with native <img> attributes, these prefixed props
are provided:
htmlWidth(number) — Override the computed width attributehtmlHeight(number) — Override the computed height attributehtmlId(string) — Set theidattribute (sinceidis used for Sanity image ID)
Setup
Environment Variables
Add your Sanity project configuration to your environment variables:
PUBLIC_SANITY_PROJECT_ID=your-project-id
PUBLIC_SANITY_DATASET=productionFor SvelteKit, these should be in your .env file and prefixed with PUBLIC_ to be available on the client side.
Tips
Choosing the right mode
If you are providing only one dimension (width or height, but not both), it
doesn't matter since the behavior will be the same.
- Contain mode will treat the dimensions you provide as boundaries, resizing the image to fit inside of them. The output image will match the aspect ratio of the original image (i.e., no cropping will occur).
- Cover mode will treat the dimensions you provide as a container, resizing the image to completely fill the dimensions. The output image will match the aspect ratio of the dimensions you provide.
Using priority and natural props
Priority images are for above-the-fold content that should load immediately:
<!-- Hero image that should load first -->
<Image
image={heroImage}
width={1200}
height={600}
priority={true}
alt="Page hero"
class="w-full h-auto"
/>Natural sizing preserves the original image dimensions without any resizing:
<!-- Profile picture at its natural size -->
<Image
image={avatarImage}
natural={true}
alt="User avatar"
class="rounded-full max-w-20"
/>
<!-- Logo that should maintain its exact proportions -->
<Image
image={logoImage}
natural={true}
alt="Company logo"
class="h-12 w-auto"
/>Styling your images
I recommend setting something like the following Tailwind classes for images in your project,
then overriding styles as needed. This will ensure images act like block-level
elements with infinitely scalable contents even with the width and height
attributes set:
/* Add to your global CSS */
img {
@apply block max-w-full w-full h-auto;
}Or apply directly to images:
<Image {image} class="block max-w-full w-full h-auto" alt="My image" />Here's an example of how that works when using a 3-column grid:
<script lang="ts">
import { Image } from 'sanity-svelte-image';
let { images } = $props();
</script>
<div class="grid grid-cols-3 gap-4 max-w-screen-xl px-5 mx-auto">
{#each images as image (image._id)}
<div>
<Image
{image}
width={390}
sizes="(min-width: 1240px) 390px, calc((100vw - 40px - 30px) / 3)"
alt="Gallery image"
class="block max-w-full w-full h-auto"
/>
</div>
{/each}
</div>If you need these images to all match in height, switch to cover mode:
<Image
{image}
width={390}
height={260}
mode="cover"
sizes="(min-width: 1240px) 390px, calc((100vw - 40px - 30px) / 3)"
alt="Gallery image"
/>Background images with Svelte
Using the Image component for background images:
<script lang="ts">
import { Image } from 'sanity-svelte-image';
let { backgroundImage } = $props();
</script>
<section class="relative py-24">
<Image
image={backgroundImage}
width={1440}
class="absolute inset-0 w-full h-full object-cover select-none z-0"
alt=""
/>
<div class="relative z-10">
<h1 class="text-4xl font-bold">Your big hero copy</h1>
<a href="/signup/" class="inline-block mt-4 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Get started
</a>
</div>
</section>Fetching data from Sanity via GROQ
If you're using Sanity's GROQ query language to fetch data, here is how I recommend fetching the image fields:
// For a simple image field
image {
...
}
// If you need the asset reference directly
image {
asset->{
_id,
_ref
},
hotspot { x, y },
crop {
bottom,
left,
right,
top,
}
}Type Definitions
The component exports the following TypeScript types:
import type {
SanityImage, // Main component props type
SanityImageAsset // Sanity image object type
} from 'sanity-svelte-image';
// You can also import the baseUrl helper
import { baseUrl } from 'sanity-svelte-image';Roadmap
- [ ] Low-quality image preview (LQIP) support
- [ ]
<picture>element support with<source>tag rendering - [ ] Blur-up transitions
License
MIT License - see LICENSE file for details.
Contributing
Issues and pull requests are welcome! Please check the existing issues before creating a new one.
Credits
This component is inspired by and based on the excellent sanity-image React component by Corey Ward. The Svelte implementation provides the same powerful features adapted for the Svelte ecosystem.
