remark-picture
v1.1.1
Published
A Remark plugin that converts Markdown `<img>` elements into modern `<picture>` elements
Maintainers
Readme
📸 remark-picture
A Remark plugin that converts Markdown <img> elements into modern <picture> elements with responsive and next-gen image formats (WebP, AVIF, etc.).
Works great together with picture-converter — a companion tool that converts your legacy
.png,.jpg, and.giffiles into efficient next-gen formats.
Why use remark-picture?
Traditional Markdown images are static and can’t take advantage of modern browser optimizations. With remark-picture, you can:
✅ Automatically generate <picture> elements from 
✅ Serve WebP and AVIF images for smaller size and faster loading
✅ Define responsive sources via media queries
✅ Add pixel-density variants (1x, 2x, etc.)
✅ Use custom URL templates to integrate with CDNs or build pipelines
✅ Stay fully typed with TypeScript
Installation
npm install remark-picture🚀 Quick Start
import {unified} from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
import remarkPicture from 'remark-picture'
const file = await unified()
.use(remarkParse)
.use(remarkPicture)
.use(remarkRehype)
.use(rehypeStringify)
.process('')
console.log(String(file))✨ Example
Input Markdown
Output HTML
<picture>
<source srcset="images/sunset.avif" type="image/avif">
<source srcset="images/sunset.webp" type="image/webp">
<img src="images/sunset.jpg" alt="Sunset">
</picture>With Media Queries and Densities
<picture>
<source srcset="/mobile/sunset.webp, /mobile/[email protected] 2x"
type="image/webp" media="(max-width: 480px)">
<source srcset="/desktop/sunset.webp, /desktop/[email protected] 2x"
type="image/webp" media="(min-width: 481px)">
<img src="images/sunset.jpg" alt="Sunset">
</picture>🧩 Usage
import {unified} from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
import remarkPicture from 'remark-picture'
const file = await unified()
.use(remarkParse)
.use(remarkPicture, {
formatMapping: {
jpg: ['avif', 'webp'],
png: ['webp']
},
media: {
mobile: {
urlTemplate: '/mobile/{basename}.{ext}',
query: '(max-width: 480px)',
pixelDensity: [1, 2]
},
desktop: {
urlTemplate: '/desktop/{basename}.{ext}',
query: '(min-width: 481px)',
pixelDensity: [1, 2]
}
}
})
.use(remarkRehype)
.use(rehypeStringify)
.process('')
console.log(String(file))⚙️ Options
| Option | Type | Default | Description |
| ------------------ |----------------------------------------------------------| -------------------------- | -------------------------------------------------------------------------- |
| formatMapping | Record<ImageExtension, ImageExtension[]> | { jpg/png → avif, webp } | Defines which formats to generate from which source |
| media | Record<string, { urlTemplate, query?, pixelDensity? }> | – | Define responsive sources (e.g. mobile vs desktop) |
| pixelDensity | number | 1 | Default pixel density multiplier |
| imageUrlTemplate | string | – | Template for generated image URLs, e.g. "/images/{basename}@{pd}x.{ext}" |
| imageToPicture | (image, convert, pictureBuilder) => Picture | – | Custom function for full control over generation |
🔧 URL Templating
Template strings (for imageUrlTemplate or media.urlTemplate) can use:
| Placeholder | Example | Description |
| --------------- | ---------------------- |----------------------------|
| {dir} | /assets/images/ | Original image directory |
| {basename} | photo | Filename without extension |
| {originalExt} | jpg | Original image extension |
| {ext} | webp | Target image extension |
| {pd} | 2 | Pixel density (e.g. 1, 2) |
| {basepath} | /assets/images/photo | Directory + basename |
Example:
imageUrlTemplate: "/cdn/{basename}@{pd}x.{ext}"🧩 Custom Conversion
You can fully control how <picture> elements are generated:
const adaptiveOptions: Options = {
formatMapping: {png: ['webp']},
media: {
mobile: {
query: '(max-width: 480px)',
urlTemplate: '{dir}mobile/{basename}-{pd}x.{ext}',
pixelDensity: [2, 3]
},
desktop: {
query: '(min-width: 481px)',
urlTemplate: '{basepath}.{ext}'
}
}
}
function customImageToPicture(image: Image, convert: ConvertFn): Picture | PictureBuilder | undefined {
if (image.url.includes('icon')) {
// skip conversion for icons
return undefined
}
if (image.url.startsWith('adaptive-images/')) {
// use custom options
return convert(adaptiveOptions)
}
// use default behavior
return convert()
}
...
use(remarkPicture, {imageToPicture: customImageToPicture})🧰 Default Format Mapping
{
png: ['avif', 'webp'],
jpg: ['avif', 'webp'],
jpeg: ['avif', 'webp'],
gif: ['webp'],
apng: ['webp']
}Contributing
Contributions, issues, and feature requests are welcome!
Feel free to open an issue or
submit a pull request.
Related
Picture Converter — converts old
image formats (.png, .jpg, .gif) to modern, efficient formats (.webp, .avif) automatically.
