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

@addon-core/inject-css

v0.4.0

Published

A lightweight TypeScript-friendly library to inject CSS into browser extensions (Manifest V2 & V3)

Readme

@addon-core/inject-css

npm version npm downloads CI License: MIT

Insert and remove CSS code or extension stylesheets in browser tabs with one typed API for Manifest V2 and Manifest V3.

@addon-core/inject-css selects the correct native adapter, validates the target before delivery, and keeps unsupported browser behavior explicit.

  • One target model for the top frame, all frames, selected frames, or selected documents
  • Runtime validation that matches the TypeScript contract
  • Ordered stylesheet injection
  • Matching stylesheet removal where the browser exposes a native removal API
  • Stable package errors for invalid, unsupported, failed, and timed-out operations
  • No silent selector fallback and no extra frame-enumeration permissions

Install

npm install @addon-core/inject-css
pnpm add @addon-core/inject-css

Your extension still needs the native permissions required for CSS injection, including scripting in MV3 and appropriate host or activeTab access. The package does not modify the manifest.

Quick start

import injectCss from "@addon-core/inject-css";

const injector = injectCss({
  target: {tabId: 123},
});

await injector.insert("body { background: #f5f5f5; }");

// The source, target, and origin match the insertion.
await injector.remove("body { background: #f5f5f5; }");

The package detects the current manifest version automatically. Insertion uses tabs.insertCSS in MV2 and scripting.insertCSS in MV3; removal uses the matching native removal API when available.

Choose what to target

Every injector has exactly one target. Selectors are mutually exclusive in TypeScript and validated again at runtime.

| Need | Target | | --- | --- | | Main frame | {tabId: 123} | | Every injectable frame | {tabId: 123, allFrames: true} | | One frame | {tabId: 123, frameIds: [7]} | | Selected frames | {tabId: 123, frameIds: [0, 7, 12]} | | Selected documents | {tabId: 123, documentIds: ["document-a", "document-b"]} |

const topFrame = injectCss({
  target: {tabId: 123},
});

const selectedFrames = injectCss({
  target: {tabId: 123, frameIds: [0, 7]},
});

const allFrames = injectCss({
  target: {tabId: 123, allFrames: true},
});

allFrames accepts only the literal true. Omitting a selector means the top frame; there is no allFrames: false mode.

documentIds require an MV3 runtime with native document targeting. An unsupported target throws UnsupportedInjectCssTargetError; the package never drops documentIds or falls back to a broader target.

allFrames remains one native browser operation. The package does not enumerate frames or promise an exhaustive frame snapshot.

Insert CSS code

await injector.insert(`
  html {
    color-scheme: dark;
  }

  body {
    background: #111;
    color: #eee;
  }
`);

The CSS source must be a non-empty string.

Insert CSS files

await injector.file("styles/content.css");

await injector.file([
  "styles/reset.css",
  "styles/theme.css",
]);

File lists must be non-empty and every path must be a non-empty string. Files are injected in the provided order. In MV2, one file completes for the requested target before the next file starts, preserving CSS cascade order.

Remove CSS

Remove CSS code with remove() and extension stylesheets with removeFile():

await injector.remove("body { background: #f5f5f5; }");

await injector.removeFile("styles/content.css");

await injector.removeFile([
  "styles/reset.css",
  "styles/theme.css",
]);

Removal uses the injector's current target and origin. The CSS source, file list, target, and origin must match the values used for insertion. Removing a stylesheet that is not present is a native no-op.

MV3 uses scripting.removeCSS. MV2 uses tabs.removeCSS only when the current browser exposes it; otherwise removal rejects with UnsupportedInjectCssOperationError. This capability is checked when removal is requested, so insertion remains available in MV2 browsers without tabs.removeCSS.

MV2 removes multiple files sequentially in the provided order, matching its insertion behavior. runAt affects insertion only because the MV2 removal API has no corresponding field.

Reuse an injector

Replace the complete target with target():

injector
  .target({tabId: 123, frameIds: [7]})
  .target({tabId: 123, allFrames: true});

The second call replaces the previous selector instead of merging with it. A validation failure leaves the existing target unchanged.

Update only execution options with options():

injector.options({
  origin: "USER",
  timeoutMs: 8_000,
});

options() never accepts or changes a target. Passing an explicit undefined resets that option instead of retaining its previous value:

injector.options({
  origin: undefined,
  timeoutMs: undefined,
});

The next operation then uses the native origin default and the package's default timeout.

Execution options

The portable baseline is to omit adapter-specific options:

const injector = injectCss({
  target: {tabId: 123},
  origin: "AUTHOR",
  timeoutMs: 5_000,
});

| Option | MV2 | MV3 | | --- | --- | --- | | origin | Mapped to author or user | Passed as AUTHOR or USER | | timeoutMs | Supported; default 4_000 ms | Supported; default 4_000 ms | | matchAboutBlank | Passed only when explicitly set | Rejected; no native equivalent | | runAt | Passed to tabs.insertCSS | Rejected; no native equivalent |

When matchAboutBlank is omitted, the package preserves the native default instead of forcing it to true.

Explicit unsupported options throw UnsupportedInjectCssOptionError. They are never ignored silently.

Handle failures

import {InjectCssBaseError} from "@addon-core/inject-css";

try {
  await injector.file("styles/content.css");
} catch (error) {
  if (error instanceof InjectCssBaseError) {
    console.error(error.code, error.message, error.cause);
  } else {
    throw error;
  }
}

Every rejected package operation exposes an error derived from InjectCssBaseError with a stable code. Delivery and timeout errors also retain the request target and expose operation as "insert" or "remove". For explicit MV2 frame targets, a delivery error may contain an InjectCssFrameDeliveryError cause with code ERR_INJECT_CSS_FRAME_DELIVERY, the failed tabId, frameId, operation, and native cause. Prefer code when errors may cross realms or multiple copies of the dependency may exist.

Known validation and adapter incompatibilities fail before delivery. Browser capabilities discovered only by a native call are normalized after that call.

What Promise<void> means

Native CSS injection and removal APIs do not provide a portable per-frame result. insert(), file(), remove(), and removeFile() therefore resolve with no value.

A resolved promise means the native operation completed. It does not prove that CSS was inserted or removed in every requested frame. A rejected multi-target operation is not transactional: some targets or earlier files may already have completed the requested change.

In MV2, a timeout stops the package from starting later files in a sequential insertion or removal batch. In MV3, the complete file list is handed to the browser in one native call before a timeout can occur. In either adapter, a timeout cannot cancel a native browser operation that is already in progress.

API reference

The factory is available as both a default and named export:

import injectCss from "@addon-core/inject-css";
import {injectCss} from "@addon-core/inject-css";
interface InjectCssContract {
  insert(css: string): Promise<void>;
  file(files: string | NonEmptyReadonlyArray<string>): Promise<void>;
  remove(css: string): Promise<void>;
  removeFile(files: string | NonEmptyReadonlyArray<string>): Promise<void>;
  target(target: InjectCssTarget): this;
  options(options: InjectCssExecutionOptionsPatch): this;
}

Runtime exports:

injectCss
InjectCssBaseError
InjectCssDeliveryError
InjectCssFrameDeliveryError
InjectCssTimeoutError
InvalidInjectCssCodeError
InvalidInjectCssFilesError
InvalidInjectCssOptionsError
InvalidInjectCssTargetError
UnsupportedInjectCssOptionError
UnsupportedInjectCssOperationError
UnsupportedInjectCssTargetError

Core type exports:

InjectCssContract
InjectCssOptions
InjectCssExecutionOptions
InjectCssExecutionOptionsPatch
InjectCssOperation
InjectCssOrigin
InjectCssTarget
InjectCssTopFrameTarget
InjectCssAllFramesTarget
InjectCssFramesTarget
InjectCssDocumentsTarget
InjectCssErrorCode
NonEmptyReadonlyArray

Design boundaries

The package focuses on portable programmatic CSS insertion and removal. It does not enumerate frames, discover document IDs, track which stylesheets were inserted, aggregate application-specific per-frame results, or claim atomic delivery across targets.

Callers remain responsible for retaining the exact source, target, and origin needed for later removal.

License

MIT