npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

custom-responsive-image

v0.1.1

Published

Framework-agnostic responsive image component with strict aspect ratio, object-fit (contain/cover/adaptive), and automatic small-image (icon/illustration) handling.

Downloads

32

Readme

custom-responsive-image

A framework-agnostic, headless responsive image component.

The component mounts a container + <img> into any DOM element and gives you:

  • Strict aspect ratio — pass aspectRatio and the container box always keeps it.
  • object-fit: contain | cover | auto — auto (default) picks cover when the image is dimensionally bigger than its box, otherwise contain.
  • Small image (icon/illustration) handling — images smaller than a global threshold are automatically contained and centered, never upscaled or cropped. Can also be forced per instance.
  • Fully responsive — the container is width: 100% of its parent; no pixel sizes required.

Install

npm install custom-responsive-image

Quick start

import { createResponsiveImage } from 'custom-responsive-image';

// 16:9 box, image covers the box when it is bigger than it, else it fits.
const hero = createResponsiveImage(document.querySelector('#hero'), {
  src: '/photos/hero.jpg',
  alt: 'Hero photo',
  aspectRatio: '16 / 9',
});

// 1:1 icon box, always contain + centered (auto-detected as small, 64px).
const icon = createResponsiveImage(document.querySelector('#icon'), {
  src: '/icons/logo.svg',
  alt: 'Logo',
  aspectRatio: '1 / 1',
});

// Cover always.
const cover = createResponsiveImage(document.querySelector('#cover'), {
  src: '/photos/banner.jpg',
  aspectRatio: 4 / 3,
  objectFit: 'cover',
});

Options

| Option | Type | Default | Description | | --------------- | ------------------------------- | ---------- | ---------------------------------------------------------------------------- | | src | string | (required) | Image URL. | | alt | string | '' | Alt text. | | aspectRatio | number \| string | — | Strict container ratio, e.g. 16 / 9, '16:9', 1.5. Omit for fluid. | | objectFit | 'contain' \| 'cover' \| 'auto' | 'auto' | auto = cover if image is dimensionally bigger than the box, else contain. | | small | boolean | auto | Force small-image treatment (contain + centered). | | className | string | — | Extra class(es) on the <img> (state modifier classes are added after them). | | badge | ResponsiveImageBadge \| null | — | Optional corner badge (icon / text / icon+text / text+icon). | | loading | 'lazy' \| 'eager' | 'lazy' | Native loading attribute. | | fetchPriority | 'high' \| 'low' \| 'auto' | 'auto' | Native fetchpriority attribute. | | crossOrigin | string | — | Native crossorigin attribute. |

Behaviour

Aspect ratio is strict

When aspectRatio is provided the container uses CSS aspect-ratio, so the box keeps the ratio at every width. Without it the container is fluid — the <img> is width: 100%; height: auto and the container grows with the image.

objectFit: 'auto' (default)

The component measures the image's natural size and the container box, then:

  • image dimension bigger than the box → cover
  • otherwise → contain

The decision is re-evaluated on image load and on container resize (via ResizeObserver, when available).

Small images (icons / illustrations)

An image is small when its largest natural dimension is below a global threshold (default 160px). Small images are always:

  • object-fit: contain and centered,
  • never upscaled beyond their natural size,
  • never cropped.
import { setResponsiveImageConfig } from 'custom-responsive-image';

setResponsiveImageConfig({ smallImageThreshold: 100 });

Pass small: true / small: false to force the behaviour for a specific image and override auto-detection.

Badge

An optional badge can be overlaid on the image box. It supports icon only, text only, icon+text or text+icon, any of the four corners, and light/dark color themes:

createResponsiveImage(el, {
  src: 'photo.jpg',
  aspectRatio: '16 / 9',
  badge: {
    icon: '★',
    text: 'Featured',
    order: 'icon-text',        // 'icon-text' | 'text-icon' (default 'icon-text')
    position: 'bottom-right',  // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
    theme: 'dark',             // 'dark' | 'light' (default 'dark')
  },
});
  • icon accepts an emoji/glyph or raw SVG markup (any string containing <).

  • Omit icon or text to show only the other.

  • Themes 'dark' and 'light' use the --ri-badge-color and --ri-badge-bg CSS variables, so a custom theme is just a CSS override:

    .ri__badge {
      --ri-badge-color: #ffffff;
      --ri-badge-bg: #b54507;
    }
  • Pass badge: null (or omit it) to hide the badge; update({ badge: {...} }) replaces it.

Controller API

const controller = createResponsiveImage(container, options);

controller.element; // the created container div
controller.image; // the created <img>
controller.isSmall; // whether it is treated as small
controller.getNaturalSize(); // { width, height } | null
controller.getObjectFit(); // 'contain' | 'cover'
controller.refresh(); // re-evaluate against current layout/config
controller.update({ src: 'new.jpg', aspectRatio: '4 / 3' });
controller.destroy(); // remove from DOM + cleanup

Global config

import {
  getResponsiveImageConfig,
  setResponsiveImageConfig,
  resetResponsiveImageConfig,
} from 'custom-responsive-image';

Styles

Base styles are injected once automatically on first use. You can import the raw CSS or inject it manually:

import { RESPONSIVE_IMAGE_CSS, injectResponsiveImageStyles } from 'custom-responsive-image';

Demo

Live demo: https://codespriha.github.io/custom-responsive-image/demo/

Run it locally:

npm run build && npm run build:demo
npx serve .

Then open the demo at the served path.

License

MIT