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

theme-toggle-circular

v1.0.0

Published

Framework-agnostic circular reveal theme switcher using the View Transition API. Works with Tailwind CSS.

Readme

theme-toggle-circular

Circular reveal theme switcher, framework-agnostic, zero dependency. Basé sur document.startViewTransition() + clip-path: circle() animé via la Web Animations API.

Pourquoi pas juste copier le code rdsx.dev directement ?

Le site rdsx.dev te donne le concept CSS/JS brut, mais deux problèmes en usage réel :

  1. Rien ne gère le sens du switch (dark→light vs light→dark). Sans ça t'as le bug que t'as eu dans ta version Vue : light → dark → light → dark qui flicker, parce que t'animes toujours le même pseudo-élément peu importe la direction.
  2. Rien n'est packagé — tu dois recopier la logique dans chaque projet/framework.

Ce package règle les deux : logique de direction encapsulée dans la classe, et une seule API qui marche partout.

Installation

Pas de build step nécessaire, c'est de l'ESM pur. Deux options :

# Si tu publies/utilises via npm dans un projet Vite/Webpack/etc.
npm install theme-toggle-circular

ou juste copie src/index.js + src/theme-toggle.css dans ton projet.

Setup obligatoire

1. Importer le CSS (une seule fois, globalement)

import "theme-toggle-circular/css";

Sans ce CSS, le crossfade par défaut du navigateur rentre en conflit avec l'animation clip-path → tu vois un effet de double-image/ghosting.

2. Anti-FOUC (flash of unstyled content)

Avant que ton JS/framework hydrate, mets un script inline dans le <head> qui lit localStorage et applique la classe dark avant le premier paint :

<script>
  (function () {
    const stored = localStorage.getItem("theme");
    const dark = stored === "dark" ||
      (!stored && matchMedia("(prefers-color-scheme: dark)").matches);
    document.documentElement.classList.toggle("dark", dark);
  })();
</script>

C'est indépendant du package — c'est une contrainte de tout système dark-mode côté client, pas spécifique à ce repo.

Usage — Vanilla JS

import { initThemeToggle } from "theme-toggle-circular";

const toggleTheme = initThemeToggle();
button.addEventListener("click", toggleTheme);

Usage — Vue 3 (Composition API)

// composables/useThemeToggle.ts
import { ThemeToggle } from "theme-toggle-circular";
import { onMounted, onUnmounted, ref } from "vue";

export function useThemeToggle() {
  let instance: ThemeToggle;
  const theme = ref<"dark" | "light">("light");

  onMounted(() => {
    instance = new ThemeToggle();
    theme.value = instance.getTheme();
  });

  onUnmounted(() => instance?.destroy());

  function toggle(event?: MouseEvent) {
    instance.toggle(event);
    theme.value = instance.getTheme();
  }

  return { theme, toggle };
}
<script setup lang="ts">
import { useThemeToggle } from "@/composables/useThemeToggle";
const { toggle } = useThemeToggle();
</script>

<template>
  <button @click="toggle">Toggle</button>
</template>

C'est exactement le point faible que t'avais dans Dark_Theme_Toggle : là, theme.value est mis à jour après l'appel à instance.toggle(), jamais avant/pendant l'animation — donc pas de désync entre la réactivité Vue et le snapshot du navigateur.

Usage — React

import { useEffect, useRef } from "react";
import { ThemeToggle } from "theme-toggle-circular";

export function useThemeToggle() {
  const ref = useRef<ThemeToggle>();

  useEffect(() => {
    ref.current = new ThemeToggle();
    return () => ref.current?.destroy();
  }, []);

  return (event?: React.MouseEvent) =>
    ref.current?.toggle(event?.nativeEvent as MouseEvent);
}

Usage — Svelte

<script>
  import { initThemeToggle } from "theme-toggle-circular";
  const toggleTheme = initThemeToggle();
</script>

<button on:click={toggleTheme}>Toggle</button>

Tailwind CSS

Tailwind v4 (ton cas)

Tailwind v4 utilise prefers-color-scheme par défaut pour dark:. Comme ce package pilote le thème via une classe (localStorage + toggle manuel), il faut basculer Tailwind en mode "class" avec la nouvelle syntaxe CSS-first :

/* app.css, à côté de tes @import "tailwindcss"; */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

Tailwind v3

// tailwind.config.js
export default {
  darkMode: "class",
  // ...
};

Erreurs fréquentes

| Erreur | Cause | Fix | |---|---|---| | Flicker light → dark → light au clic | Toujours animer ::view-transition-new(root) peu importe la direction | Utiliser pseudoElement conditionnel selon wasDark (déjà géré dans ce package) | | Double image / ghosting pendant l'animation | CSS theme-toggle.css pas importé | Importer le CSS globalement | | Flash blanc/noir au chargement de page | Pas d'anti-FOUC | Script inline dans <head>, voir plus haut | | Animation ne joue pas du tout | Navigateur sans support de startViewTransition (vieux Safari) | Le package fallback automatiquement en switch instantané, c'est normal | | Le cercle part du mauvais endroit | Event pas transmis à toggle()/setTheme() | Toujours passer l'event du clic, sinon ça part du centre de l'écran | | Classe dark pas prise en compte par Tailwind | darkMode pas en mode class (v3) ou @custom-variant absent (v4) | Voir section Tailwind ci-dessus |

Compatibilité navigateurs

startViewTransition est supporté sur Chrome/Edge 111+ et Safari 18+. Firefox l'a ajouté récemment (à vérifier selon ta cible). Le package dégrade proprement : sans support, le thème change instantanément sans animation — aucune erreur, aucun crash.

Structure

src/
├── index.js          # classe ThemeToggle + helper initThemeToggle()
├── index.d.ts         # types TS
└── theme-toggle.css   # CSS obligatoire (z-index + désactivation du crossfade)
examples/
└── vanilla.html        # démo autonome, ouvrable direct dans le navigateur

License

MIT