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

alpine-rc

v0.1.4

Published

Alpine.js component plugin — global styles by default, scoped styles on demand, reactive props, slots

Readme

alpine-rc

Alpine.js plugin for reusable HTML components. Global styles work out of the box — no config needed. Scoped styles and Shadow DOM isolation are opt-in.

Installation

npm install alpine-rc
import Alpine from 'alpinejs'
import alpineRc from 'alpine-rc'

Alpine.plugin(alpineRc)
Alpine.start()

CDN

Load before Alpine:

<script src="https://unpkg.com/alpine-rc/dist/alpine-rc.min.js" defer></script>
<script src="https://unpkg.com/alpinejs/dist/cdn.min.js" defer></script>

Component files

A component is an .html file with a <template> wrapper:

<!-- components/card.html -->
<template>
  <style scoped>
    .card {
      border: 1px solid #ddd;
      padding: 16px;
      border-radius: 8px;
    }
    .card h2 {
      margin: 0;
    }
  </style>

  <div class="card">
    <h2 x-text="title"></h2>
    <slot></slot>
  </div>
</template>

The <template> wrapper is stripped at load time. Styles are handled automatically — see Styles.


Basic usage

<div x-component.url="'./components/card.html'" :title="post.title">
  <template x-slot>
    <p x-text="post.body"></p>
  </template>
</div>

From an on-page template

<template id="card">
  <div class="card">
    <h2 x-text="title"></h2>
    <slot></slot>
  </div>
</template>

<div x-component="'card'" :title="post.title"></div>

Props

Pass data to the component via :attr or x-bind:attr bindings on the host element. Props are reactive — when the bound value changes, the component updates without re-rendering.

<div
  x-component.url="'./components/card.html'"
  :title="post.title"
  :count="post.likes"
  :active="selectedId === post.id"
></div>

Inside the component template, props are available directly:

<template>
  <div :class="{ active }">
    <h2 x-text="title"></h2>
    <span x-text="count"></span>
  </div>
</template>

The component also inherits the full Alpine scope of the host element, so parent data like $store is accessible too.


Slots

Default slot

<div x-component.url="'./card.html'" :title="post.title">
  <template x-slot>
    <p>This goes into the default slot</p>
  </template>
</div>
<!-- card.html -->
<template>
  <div class="card">
    <h2 x-text="title"></h2>
    <slot></slot>
  </div>
</template>

Named slots

<div x-component.url="'./card.html'" :title="post.title">
  <template x-slot>Main content</template>
  <template x-slot="footer">
    <button @click="save">Save</button>
  </template>
</div>
<!-- card.html -->
<template>
  <div class="card">
    <h2 x-text="title"></h2>
    <slot></slot>
    <footer><slot name="footer"></slot></footer>
  </div>
</template>

Slots without matching content show their fallback children:

<slot>This renders if no slot content is provided</slot>

Styles

Default — global styles work as-is

No configuration needed. Tailwind, CSS, SCSS, and any <link> stylesheets apply to the component naturally because it renders in the regular DOM.

<style> tags inside the component are extracted and injected into <head> once per component source.

<!-- x-component.url="'./card.html'" (no modifier) -->
<template>
  <style>
    /* Injected into <head> once — global scope */
    .card {
      padding: 16px;
    }
  </style>
  <div class="card"></div>
</template>

.scoped — isolated styles via data attribute

<style scoped> is transformed so selectors only match elements inside this component instance, using a data-arc-* attribute (similar to Vue SFC). Global styles and Tailwind still work.

<div x-component.url.scoped="'./card.html'"></div>
<!-- card.html -->
<template>
  <style scoped>
    /* Scoped: only applies inside this component */
    .card {
      padding: 16px;
    }
  </style>

  <style>
    /* Still global — injected to <head> as-is */
    .badge {
      border-radius: 99px;
    }
  </style>

  <div class="card">...</div>
</template>

The plugin generates a stable data-arc-[hash] attribute, adds it to every element in the template, and transforms the CSS selectors accordingly.

.isolated — Shadow DOM

Full style encapsulation. Global CSS and Tailwind do not apply inside the component.

<div x-component.url.isolated="'./card.html'"></div>

To adopt global stylesheets into the shadow root:

<div x-component.url.isolated.with-styles="'./card.html'"></div>

Modifiers reference

| Modifier | Description | | ----------------------- | ---------------------------------------------- | | .url | Load template from a same-origin URL | | .url.external | Load template from a cross-origin URL | | .scoped | Scope <style scoped> via data attribute | | .isolated | Render in Shadow DOM | | .isolated.with-styles | Shadow DOM + adopt global document stylesheets |


Lifecycle events

All events bubble and are dispatched on the host element.

el.addEventListener('rc:loading', ({ detail }) => console.log('loading', detail.source))
el.addEventListener('rc:loaded', ({ detail }) => console.log('loaded', detail.source))
el.addEventListener('rc:error', ({ detail }) => console.log('error', detail.error))

| Event | Fires when | Detail | | ------------ | ----------------------------------- | ------------------- | | rc:loading | URL fetch starts (.url only) | { source } | | rc:loaded | Component rendered | { source } | | rc:error | Expression, fetch, or render failed | { source, error } |


Cross-origin URLs

Same-origin by default. Use .external to allow cross-origin:

<div x-component.url.external="'https://cdn.example.com/components/card.html'"></div>

TypeScript

Types are included. No additional setup needed.

import type { RcLoadedEvent } from 'alpine-rc'

el.addEventListener('rc:loaded', (e: RcLoadedEvent) => {
  console.log(e.detail.source)
})

Production: static HTML baking

At build time you can pre-render all components into static HTML using vite-plugin-bake-alpine-components.

npm i -D vite-plugin-bake-alpine-components
// vite.config.js
import bakeAlpineComponents from 'vite-plugin-bake-alpine-components'

export default {
  plugins: [bakeAlpineComponents()],
}

Use an explicit split between build-time and runtime directives:

  • s-* directives are baked at build time by the Vite plugin.
  • x-*, :*, and @* directives are left untouched for Alpine runtime.

alpine-rc also normalizes s-* to their Alpine equivalents in dev/runtime mode so behavior stays consistent:

| Directive | Baked output | Dev/runtime equivalent | | --- | --- | --- | | s-text="expr" | static text | x-text | | s-html="expr" | static innerHTML | x-html | | s-class="expr" | merged into class | :class | | s-style="expr" | merged into style | :style | | s-show="expr" | style="display:none" if falsy | x-show | | s-bind:attr="expr" | static attribute | :attr | | <template s-for="x in list"> | expanded loop | <template x-for> | | <template s-if="expr"> | included or removed | <template x-if> |

This keeps component authoring simple: use s-* for baked/static output and x-* for client reactivity.

Rules and constraints

s-if is only valid on <template> elements. For conditional visibility on a regular element use s-show instead:

<!-- correct: conditional block -->
<template s-if="isAdmin">
  <p>Admin panel</p>
</template>

<!-- correct: show/hide an element -->
<span s-show="label" s-text="label"></span>

<!-- wrong: s-if on a non-template element is not supported -->
<span s-if="label">...</span>

Prop names must be lowercase. HTML normalises attribute names to lowercase in the DOM. A binding like :showHint="..." becomes :showhint by the time alpine-rc reads it. Use all-lowercase names consistently in both the host binding and the component template:

<!-- host -->
<div x-component="'my-card'" :showhint="true"></div>

<!-- component template -->
<template s-if="showhint">...</template>

x-data is always a runtime directive — never use s-bind:x-data. Props are available inside x-data expressions through the scope chain, so write x-data directly and read props by name:

<!-- correct -->
<div x-data="{ count: Number(start) || 0 }">...</div>

<!-- wrong — s-bind:x-data breaks in dev mode -->
<div s-bind:x-data="{ count: Number(start) || 0 }">...</div>