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 🙏

© 2024 – Pkg Stats / Ryan Hefner

userpref

v1.0.7

Published

User Preferences for Web Applications

Downloads

86

Readme

NPM Version npm bundle size Static Badge Static Badge

userpref

Simple User Preferences for Web Apps.

  • 🪶 < 1 KB size and 0 dependencies.
  • 🏗️ Framework agnostic.
  • 🧱 Supports custom preferences.
  • 💻 Defaults to system preferences for theme & motion.
  • 🫙 Saves user preferences (local storage).
  • 🎨 Styles browser UI for light / dark theme.
  • 🌓 Supports light-dark() CSS function.
  • 🔗 Syncs between open tabs and windows.
  • 💥 No flash while loading theme.

Install

To use this script, you must place it in a <script> tag as the first element inside <body>.

ES Module (Recommended)

If using React or similar, you can use the userpref module import, this is a string ready to inline with a <script> tag:

npm install userpref
import { source } from "userpref";

export default function ReactRootLayout() {
  return (
    <html>
      <body>
        <script
          dangerouslySetInnerHTML={{
            __html: userpref,
          }}
        />
      </body>
    </html>
  );
}

JS

For an installation without using modules, you could grab the dist/userpref.js build from npm and copy that inside a <script> tag as the first element inside <body>.

API

Using the script tag will expose a userpref object on window.

window.userpref

All preferences are available in this object.

  • window.userpref.theme: Theme Preference
  • window.userpref.motion: Motion Preference

Preference

Each preference is represented as an object, you can use this to get and set preferences:

  • user: The user preference
  • system: The system preference
  • resolved: The resolved preference value that is currently used (readonly)

Examples

Common Usage:

// Setting User Preference:
userpref.theme.user = "dark"; // set user preference to dark theme
userpref.theme.user = "light"; // set user preference to light theme
userpref.theme.user = "system"; // set user preference to system theme

// Getting User Preference:
userpref.theme.user; // 'dark' | 'light' | 'system'

// Getting Resolved Preference:
userpref.theme.resolved; // 'dark' | 'light'

// Handle Preference Change:
window.addEventListener("userpref-change", (event) => {
  const {
    key, // 'theme' | 'motion' | ...
    preference, // { user, system, resolved }
  } = event.detail;
});

CSS

All preferences are set as data attributes on the <html> element:

<html data-theme="dark" data-motion="reduced"></html>

This can be used in CSS queries:

:root {
  --background: white;
}

[data-theme="dark"] {
  --background: black;
}

[data-motion="reduced"],
[data-motion="reduced"] *,
[data-motion="reduced"] *::after,
[data-motion="reduced"] *::before {
  transition-duration: 1ms !important;
  animation-play-state: paused !important;
}

You can also use the new light-dark() CSS function:

:root {
  --background: light-dark(white, black);
}

Custom Preferences

Register custom preferences and their initial system preference using data attributes on the script tag:

<script
  dangerouslySetInnerHTML={{
    __html: userpref,
  }}
  data-audio="muted"
/>

The example above registers an audio preference, and sets the system preference to "muted".

The default user preference will be "system" which resolves to "muted".

// Setting Custom User Preference:
window.userpref.audio.user = "enabled";
window.userpref.audio.user = "muted";
window.userpref.audio.user = "system";

// Getting Custom Resolved Preference:
window.userpref.audio.resolved; // 'enabled' | 'muted'

// Setting Custom System Preference:
window.userpref.audio.system = "enabled";
window.userpref.audio.system = "muted";

[!NOTE] System preferences are automatically updated on theme and motion. But you can set them for custom preferences.

React + TypeScript Example

If you need to access the preference using React, you can use a hook like this:

"use client";

import type { Preference, PreferenceChangeEvent } from "userpref";
import { useEffect, useState } from "react";

export const useReadPreference = (type: string) => {
  const [preference, setPreference] = useState<Preference | null>(
    typeof window === 'undefined'
      ? null
      : Object.freeze({ ...window.userpref[type] }),
  );

  useEffect(() => {
    const handleChange = (event: PreferenceChangeEvent) => {
      if (event.detail.key === type) {
        setPreference(Object.freeze({ ...event.detail.preference }));
      }
    };

    window.addEventListener('userpref-change', handleChange);
    return () => {
      window.removeEventListener('userpref-change', handleChange);
    };
  }, [type]);

  return preference;
};

[!NOTE] This example hook is only intended for reading the preferences. This is because we create a new object to easily re-render in React on change.

Example usage:

export function ToggleTheme() {
  const theme = useReadPreference("theme");

  return (
    <>
      <div>Current theme: {theme.resolved}</div>
      <button onClick={() => (window.userpref.theme.user = "dark")}>
        Use Dark Theme
      </button>
      <button onClick={() => (window.userpref.theme.user = "light")}>
        Use Light Theme
      </button>
      <button onClick={() => (window.userpref.theme.user = "system")}>
        Use System Theme
      </button>
    </>
  );
}