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

adkit-react

v1.1.2

Published

A drop-in React component for selling direct, bookable ads on your site — no AdSense required.

Downloads

102

Readme

adkit-react

A drop-in React component for selling direct, bookable ad space on your site — no AdSense, no programmatic networks, no middlemen.

npm License

Not using React? See adkit-js for the vanilla JavaScript SDK.


Overview

Add <AdkitProvider> and <AdSlot> to your app. Each slot either renders a paid advertiser's creative or a "Rent this spot" placeholder that lets visitors book directly from your page. You set a flat daily price. Adkit handles the booking flow, payment, and approval process.


Installation

npm install adkit-react

Quick Start

import { AdkitProvider, AdSlot } from "adkit-react"
import "adkit-react/styles.css"

function App() {
  return (
    <AdkitProvider siteId="your-site-id">
      <AdSlot slot="sidebar" aspectRatio="4:3" price={2500} />
    </AdkitProvider>
  )
}

Your site ID is found in the Adkit dashboard. The slot above goes live at $25/day immediately.


Setup

1. Wrap your app with <AdkitProvider>

import { AdkitProvider } from "adkit-react"
import "adkit-react/styles.css"

export default function RootLayout({ children }) {
  return (
    <AdkitProvider siteId="your-site-id">
      {children}
    </AdkitProvider>
  )
}

The provider shares your site ID across all slots in its subtree and manages the refresh lifecycle. You only need one provider per site.

2. Place <AdSlot> components where you want ad placements

import { AdSlot } from "adkit-react"

export default function Sidebar() {
  return (
    <aside>
      <AdSlot slot="sidebar" aspectRatio="4:3" price={2500} />
    </aside>
  )
}

3. Import the stylesheet

import "adkit-react/styles.css"

Import this once at the root of your app. It covers all slot and modal styles.


Components

<AdkitProvider>

Wraps your app (or any subtree) to provide site ID context and slot lifecycle management to all child <AdSlot> components.

<AdkitProvider siteId="your-site-id">
  {children}
</AdkitProvider>

| Prop | Type | Required | Description | |---|---|---|---| | siteId | string | Yes | Your Adkit site ID from the dashboard | | children | ReactNode | Yes | Your app or subtree |


<AdSlot>

Renders a single ad placement. Fetches its status from the Adkit API and renders either an active ad or a booking placeholder.

<AdSlot
  slot="sidebar"
  aspectRatio="4:3"
  price={2500}
  size="lg"
  theme="auto"
/>

Props

| Prop | Type | Default | Description | |---|---|---|---| | slot | string | — | Required. Unique placement name. Letters, numbers, hyphens, underscores only. | | aspectRatio | AspectRatio | — | Required. Slot shape — see Aspect Ratios. | | price | number | — | Required. Daily price in cents (e.g. 2500 = $25/day). See Pricing. | | siteId | string | — | Manual site ID override. Not needed inside <AdkitProvider>. | | size | "sm" \| "md" \| "lg" | "lg" | Placeholder text size. | | theme | "light" \| "dark" \| "auto" | "auto" | Color theme. "auto" follows system preference. | | className | string | — | CSS class(es) applied to the slot container. Use this to set width. | | styles | AdSlotStyles | — | Custom color overrides for the placeholder. Does not affect rendered ads. | | silent | boolean | false | Disable all analytics event tracking for this slot. |

Without a Provider

You can use <AdSlot> without a provider by passing siteId directly:

<AdSlot
  siteId="your-site-id"
  slot="sidebar"
  aspectRatio="4:3"
  price={2500}
/>

This is useful for embedding a single slot without modifying your app's root.


<BookingModal>

The booking modal is opened automatically when a visitor clicks a placeholder. It's also exported for advanced use cases where you need to trigger it programmatically.

import { BookingModal } from "adkit-react"

<BookingModal
  siteId="your-site-id"
  slot="sidebar"
  price={2500}
  onClose={() => setOpen(false)}
/>

| Prop | Type | Description | |---|---|---| | siteId | string | Your Adkit site ID | | slot | string | Slot name | | price | number | Daily price in cents (optional — modal renders without a price if omitted) | | onClose | () => void | Called when the modal is dismissed |

The modal closes on Escape key, backdrop click, or the Cancel button. Body scroll is locked while it's open. The "Book this ad" CTA redirects to https://adkit.dev/book?siteId=...&slot=...&ref=.... The price is not included in the URL — the booking page fetches it server-side to prevent manipulation.


Aspect Ratios

| Value | Ratio | Best for | |---|---|---| | "16:9" | 16:9 | Hero banners, video-style placements | | "4:3" | 4:3 | Sidebars, content blocks | | "1:1" | 1:1 | Square placements, social-style | | "9:16" | 9:16 | Vertical/mobile, stories format | | "banner" | 728:90 | Leaderboard banners |

The aspect ratio is enforced by the component via CSS aspect-ratio. Set width via className — height is always derived automatically.

// Width via Tailwind
<AdSlot slot="sidebar" aspectRatio="4:3" price={2500} className="w-[300px]" />

// Width via inline style
<AdSlot slot="sidebar" aspectRatio="4:3" price={2500} style={{ width: 300 }} />

Pricing

Setting a Price

Pass price in cents. The first time the slot mounts, it's registered at that price and becomes immediately bookable.

<AdSlot slot="sidebar" aspectRatio="4:3" price={2500} />  {/* $25/day */}
<AdSlot slot="hero" aspectRatio="16:9" price={10000} />   {/* $100/day */}

Changing a Price

Update the price prop and redeploy. The SDK sends the new value on the next slot_mount event.

  • Price increases — applied immediately, no confirmation required
  • Price decreases — you'll receive a confirmation via email or dashboard notification before the change takes effect, preventing accidental or manipulated price reductions

You can also change prices directly in the Adkit dashboard. Dashboard changes take effect immediately in either direction.

How Pricing Works Under the Hood

The price prop has two roles:

  1. During loading — displayed in the placeholder while the API fetch is in-flight, so the slot shows a price rather than a blank state
  2. After API response — replaced by the server-authoritative price from the API response

The server price always wins once the fetch resolves. Billing always uses the confirmed database price.


Slot Behavior

Loading State

On mount, the slot immediately renders with "ad space" as the label and your price prop as a hint. This prevents layout shift — the slot occupies its full dimensions before the API responds.

Active Ad State

When a booking is active, the slot renders the advertiser's creative as a full-size image linked to their destination URL. Impressions are tracked at 50% viewport visibility. Clicks are tracked on the link. If the image fails to load, the slot automatically falls back to placeholder state.

Placeholder State

When no booking is active, the slot renders a dashed-border card with "Your ad here", the server-confirmed daily price, and a "Rent this spot" CTA. Clicking opens the booking modal. Banner slots show a compact "Rent" label to fit the narrow format.


Theming

Built-in Themes

<AdSlot slot="sidebar" aspectRatio="4:3" price={2500} theme="light" />
<AdSlot slot="sidebar" aspectRatio="4:3" price={2500} theme="dark" />
<AdSlot slot="sidebar" aspectRatio="4:3" price={2500} theme="auto" />  {/* default */}

"auto" subscribes to prefers-color-scheme changes and re-renders automatically when the user switches system theme.

Custom Colors

Use the styles prop to override placeholder colors per-slot:

<AdSlot
  slot="branded"
  aspectRatio="1:1"
  price={3000}
  styles={{
    backgroundColor: "#fef3c7",
    textColorPrimary: "#92400e",
    textColorSecondary: "#d97706",
    borderColor: "#f59e0b",
  }}
/>

| Field | Description | |---|---| | backgroundColor | Placeholder background (default: transparent) | | textColorPrimary | Price text color | | textColorSecondary | Label and CTA text color | | borderColor | Dashed border color (rendered at 40% opacity, 60% on hover via color-mix()) |

These styles apply to the placeholder only. When an active ad is displayed, the advertiser controls their creative.

Border color requires color-mix() support: Chrome 111+, Firefox 113+, Safari 16.2+.

Size Presets

The size prop scales the label, price, and CTA text together:

| Value | Best for | |---|---| | "sm" | Compact slots under ~200px wide | | "md" | Standard slots 200–400px wide | | "lg" | Large/hero slots over 400px wide (default) |


Hooks

useAdkit()

Access the Adkit context from any component inside <AdkitProvider>. Throws if called outside a provider — this is intentional, as missing a provider is a developer error.

import { useAdkit } from "adkit-react"

function RefreshButton() {
  const { refresh } = useAdkit()
  return <button onClick={refresh}>Refresh ads</button>
}

Context values

| Value | Type | Description | |---|---|---| | siteId | string | The site ID passed to the provider | | refresh | () => void | Re-fetches all slots in the subtree | | refreshKey | number | Increments on each refresh — used internally by <AdSlot> |

refresh()

Calling refresh() does the following:

  1. Clears the registered slot set (duplicate detection resets)
  2. Clears the mounted slots set (mount events re-fire)
  3. Increments refreshKey, triggering a re-fetch in every <AdSlot> in the subtree

Use this after SPA navigation or any time you want slots to re-check their booking status.

// Re-fetch on route change
const { refresh } = useAdkit()
const pathname = usePathname()

useEffect(() => {
  refresh()
}, [pathname])

Analytics Events

The SDK sends four event types to https://adkit.dev/api/events. All events use navigator.sendBeacon() with a fetch fallback. Errors are silently swallowed — analytics never break your app.

slot_mount

Fires once per slot identity per page load, on mount (before the API responds). Carries the price prop to register or update the slot's price on the server.

{
  "type": "slot_mount",
  "siteId": "your-site-id",
  "slot": "sidebar",
  "pathname": "/blog/my-post",
  "price": 2500,
  "aspectRatio": "4:3",
  "timestamp": 1743264000000
}

price is omitted if the prop is not set.

slot_view

Fires when 50% of the slot enters the viewport via IntersectionObserver. Fires for both active ads and placeholders — this enables fill rate calculation. Fires at most once per slot per page load.

{
  "type": "slot_view",
  "slotId": "your-site-id:sidebar",
  "bookingId": "booking-abc123",
  "pathname": "/blog/my-post",
  "viewport": "1440x900",
  "timestamp": 1743264000000
}

bookingId is only present when an active ad is displayed.

slot_click

Fires when a visitor clicks an active ad. Does not fire for placeholder clicks.

{
  "type": "slot_click",
  "slotId": "your-site-id:sidebar",
  "bookingId": "booking-abc123",
  "pathname": "/blog/my-post",
  "viewport": "1440x900",
  "timestamp": 1743264000000
}

slot_duplicate

Fires when two <AdSlot> components share the same siteId:slot identity on the same page. Both slots still render.

{
  "type": "slot_duplicate",
  "siteId": "your-site-id",
  "slot": "sidebar",
  "pathname": "/blog/my-post",
  "timestamp": 1743264000000
}

Disabling Tracking

<AdSlot slot="test" aspectRatio="1:1" price={500} silent />

Error Handling

The SDK throws explicit errors for developer misconfiguration so required props fail fast during development and testing.

| Scenario | Behavior | |---|---| | Missing slot, aspectRatio, or price | Throws | | Invalid slot name | Throws | | Missing siteId (no provider, no prop) | Throws | | useAdkit() called outside provider | Throws — this is a developer error | | Duplicate slot identity | Both render, console.warn + analytics event | | API fetch timeout (5s) | Falls back to placeholder | | API non-200 or network failure | Falls back to placeholder | | Ad image load failure | Falls back to placeholder |


Next.js

adkit-react is fully compatible with the App Router. All components are marked "use client" at the bundle level — you don't need to add the directive yourself.

// app/layout.tsx
import { AdkitProvider } from "adkit-react"
import "adkit-react/styles.css"

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AdkitProvider siteId="your-site-id">
          {children}
        </AdkitProvider>
      </body>
    </html>
  )
}
// app/components/Sidebar.tsx — no "use client" needed
import { AdSlot } from "adkit-react"

export default function Sidebar() {
  return <AdSlot slot="sidebar" aspectRatio="4:3" price={2500} />
}

Pages Router

// pages/_app.tsx
import { AdkitProvider } from "adkit-react"
import "adkit-react/styles.css"

export default function App({ Component, pageProps }) {
  return (
    <AdkitProvider siteId="your-site-id">
      <Component {...pageProps} />
    </AdkitProvider>
  )
}

TypeScript

Full TypeScript support is included. Types are exported from the package root.

import type { AspectRatio, AdSlotProps, AdSlotStyles } from "adkit-react"

const ratio: AspectRatio = "16:9"

const customStyles: AdSlotStyles = {
  borderColor: "#3b82f6",
  backgroundColor: "transparent",
  textColorPrimary: "#1a1a1a",
  textColorSecondary: "#666",
}

Slot Identity

A slot's identity is siteId:slot — the combination of your site ID and the slot name. This identity is not page-scoped. A slot named "sidebar" on /blog/post-1 and /blog/post-2 is the same placement, and a single booking covers both pages.

This is intentional: when an advertiser rents your sidebar, they expect their ad to appear everywhere your sidebar appears — not just on one URL.

If you need page-specific placements, use distinct slot names:

<AdSlot slot="homepage-hero" aspectRatio="16:9" price={5000} />
<AdSlot slot="blog-sidebar" aspectRatio="4:3" price={2500} />

Content Security Policy

If your site uses CSP headers, add:

connect-src https://adkit.dev;
img-src     https://ufs.sh;

connect-src covers the serve API (/api/serve) and analytics (/api/events). img-src covers ad creatives stored on UploadThing.


Browser Support

| Browser | Minimum Version | |---|---| | Chrome | 88+ | | Firefox | 89+ | | Safari | 15+ | | Edge | 88+ |

Required APIs: IntersectionObserver, ResizeObserver, fetch, CSS aspect-ratio.

borderColor in styles requires color-mix(): Chrome 111+, Firefox 113+, Safari 16.2+.


License

MIT