GitHub

Images

The img() and picture() helpers generate image markup that prevents CLS by requiring dimensions and handles loading priority correctly. Pulse targets 0.00 CLS — these helpers enforce the attributes that make that possible.

Importing

// In your page spec or component
import { img, picture } from '@invisibleloop/pulse/image'

img(options)

Generates an optimised <img> element:

img({
  src:      '/images/hero.jpg',
  alt:      'A hero image showing our product',
  width:    1200,
  height:   630,
  priority: true,    // → eager loading + high fetchpriority
})

Output:

<"tok-fn">class="tok-kw">img "tok-fn">src="/images/hero.jpg" "tok-fn">alt="A hero image showing our product" "tok-fn">width="1200" "tok-fn">height="630" "tok-fn">loading="eager" "tok-fn">decoding="async" "tok-fn">fetchpriority="high">

img() options

OptionTypeRequiredDescription
srcstringYesImage URL.
altstringYesAlt text. Required for accessibility. Use an empty string for decorative images.
widthnumberRecommendedIntrinsic width in pixels. Prevents CLS by reserving layout space.
heightnumberRecommendedIntrinsic height in pixels. Prevents CLS.
prioritybooleanNoIf true: loading="eager" + fetchpriority="high". Use for LCP images. Default: false.
classstringNoCSS class applied to the <img> element.
width and height are required to prevent Cumulative Layout Shift. Without them the browser cannot reserve layout space before the image loads. Pulse targets 0.00 CLS — omitting these attributes breaks that guarantee.

picture(options)

Generates a <picture> element with modern format sources and a fallback <img>. Use this to serve AVIF or WebP to browsers that support them, with JPEG/PNG as the fallback:

picture({
  src:      '/images/hero.jpg',      // fallback
  alt:      'Hero image',
  width:    1200,
  height:   630,
  priority: true,
  sources: [
    { src: '/images/hero.avif', type: 'image/avif' },
    { src: '/images/hero.webp', type: 'image/webp' },
  ],
})

Output:

<"tok-fn">class="tok-kw">picture>
  <"tok-fn">class="tok-kw">source "tok-fn">srcset="/images/hero.avif" "tok-fn">type="image/avif">
  <"tok-fn">class="tok-kw">source "tok-fn">srcset="/images/hero.webp" "tok-fn">type="image/webp">
  <"tok-fn">class="tok-kw">img "tok-fn">src="/images/hero.jpg" "tok-fn">alt="Hero image" "tok-fn">width="1200" "tok-fn">height="630" "tok-fn">loading="eager" "tok-fn">decoding="async" "tok-fn">fetchpriority="high">
</"tok-fn">class="tok-kw">picture>

picture() options

OptionTypeDescription
srcstringFallback image URL (JPEG/PNG for universal compatibility).
altstringAlt text — shared by the inner <img>.
widthnumberIntrinsic width — applied to inner <img>.
heightnumberIntrinsic height — applied to inner <img>.
prioritybooleanIf true: eager loading + high priority.
classstringCSS class on the inner <img>.
sources{src, type}[]Modern format sources in preference order (AVIF first, WebP second).

When to use priority

Set priority: true on the Largest Contentful Paint (LCP) element — typically the hero image above the fold. This tells the browser to load it with high priority, improving LCP.

Every other image should omit priority (defaults to lazy loading with loading="lazy"). Lazy images are not fetched until they approach the viewport — reducing initial page weight and speeding up load time.

Only one image per page should have priority: true. Using it on multiple images defeats the purpose — every image becomes "high priority" which is the same as no image being prioritised.

Using in a view

import { img, picture } from '@invisibleloop/pulse/image'

export default {
  route: '/blog/:slug',
  state: {},
  server: {
    data: async (ctx) => ({ post: await db.posts.findBySlug(ctx.params.slug) }),
  },
  view: (state, server) => `
    <article>
      ${picture({
        src:      server.post.heroImage,
        alt:      server.post.heroAlt,
        width:    1200,
        height:   630,
        priority: true,
        sources:  [
          { src: server.post.heroImage.replace('.jpg', '.avif'), type: 'image/avif' },
          { src: server.post.heroImage.replace('.jpg', '.webp'), type: 'image/webp' },
        ],
      })}
      <h1>${server.post.title}</h1>
    </article>
  `,
}