1.0.7 â€Ē Published 10 months ago

userpref v1.0.7

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

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>
    </>
  );
}
1.0.7

10 months ago