dark-mode-detection

📁 codingismynewgaming/dark-mode-indicator 📅 10 days ago
4
总安装量
4
周安装量
#50290
全站排名
安装命令
npx skills add https://github.com/codingismynewgaming/dark-mode-indicator --skill dark-mode-detection

Agent 安装分布

opencode 4
kilo 4
gemini-cli 4
antigravity 4
mistral-vibe 4
qwen-code 4

Skill 文档

Dark Mode Detection

Comprehensive patterns, algorithms, and detection strategies for identifying dark mode implementations in websites. This skill provides actionable detection rules for browser extensions and automated analysis tools.


When to Apply

Use this skill when:

  • ✅ Building a browser extension that detects dark mode
  • ✅ Analyzing a website’s theme implementation
  • ✅ Identifying dark mode patterns (CSS, JavaScript, frameworks)
  • ✅ Creating theme detection algorithms
  • ✅ Auditing websites for accessibility compliance
  • ✅ Reverse-engineering theme switcher implementations
  • ✅ Working with prefers-color-scheme, data-theme, or class-based toggling

Detection Priority Levels

Priority Category Impact Confidence
CRITICAL localStorage Keys Very High 95%+
CRITICAL DOM Attributes Very High 90%+
HIGH Library Signatures Very High 95%+
HIGH Class Names on html/body High 85%+
MEDIUM Toggle Buttons High 80%+
MEDIUM CSS Custom Properties Medium 70%+
LOW Computed Styles Medium 60%+
LOW matchMedia Queries Low 40%+

Detection Categories

1. localStorage Keys (CRITICAL – 95%+ Confidence)

The most reliable signal for JavaScript-based dark mode implementations.

const themeKeys = [
  'theme',                    // Most common (next-themes, custom)
  'darkMode',                 // Common variant
  'dark-mode',                // Kebab-case variant
  'color-scheme',             // Standard name
  'theme-preference',         // Explicit naming
  'vueuse-color-scheme',      // VueUse library
  'my-theme',                 // Custom (configurable)
  'vite-ui-theme',            // Vite projects
  'darkModeEnabled',          // Boolean-style
  'dark_mode_pref',           // Snake-case variant
  'user-theme',               // User-specific
  'preferred-theme',          // Preference-based
  'app-theme',                // App-specific
  'site-theme',               // Site-specific
  'colorMode',                // CamelCase variant
  'color-mode'                // Kebab-case variant
];

// Detection Algorithm
function checkLocalStorage() {
  const signals = [];
  const keys = ['theme', 'darkMode', 'dark-mode', 'color-scheme'];
  
  for (const key of keys) {
    try {
      const value = localStorage.getItem(key);
      if (value === 'dark' || value === 'enabled' || value === 'true') {
        signals.push({
          type: 'localStorage',
          key,
          value,
          confidence: 'very-high',
          implementation: 'javascript'
        });
      }
    } catch (e) {
      // Cross-origin restriction
    }
  }
  
  return signals;
}

Common Values:

  • 'dark' / 'light' (most common)
  • 'enabled' / 'disabled'
  • 'true' / 'false'
  • 'dark-theme' / 'light-theme'

2. DOM Attributes (CRITICAL – 90%+ Confidence)

Direct attributes on <html> or <body> elements.

data-theme Attribute

<!-- Most Common Pattern -->
<html data-theme="dark">
<html data-theme="light">

<!-- Detection -->
const html = document.documentElement;
if (html.getAttribute('data-theme') === 'dark') {
  // Dark mode active
}

data-bs-theme Attribute (Bootstrap 5.3+)

<!-- Bootstrap 5.3+ Pattern -->
<html data-bs-theme="dark">
<html data-bs-theme="light">

<!-- Detection -->
if (html.getAttribute('data-bs-theme') === 'dark') {
  // Bootstrap dark mode active
}

data-mui-color-scheme (Material UI)

<!-- Material UI Pattern -->
<html data-mui-color-scheme="dark">
<html data-mui-color-scheme="light">

Alternative Attributes

<html data-mode="dark">
<html color-mode="dark">
<html theme="dark">
<body data-theme="dark">

Detection Algorithm

function checkDOMAttributes() {
  const html = document.documentElement;
  const body = document.body;
  const signals = [];
  
  const attributeChecks = [
    { element: html, attribute: 'data-theme', value: 'dark' },
    { element: html, attribute: 'data-theme', value: 'light' },
    { element: html, attribute: 'data-mode', value: 'dark' },
    { element: html, attribute: 'color-mode', value: 'dark' },
    { element: html, attribute: 'data-bs-theme', value: 'dark' },
    { element: html, attribute: 'data-mui-color-scheme', value: 'dark' },
    { element: body, attribute: 'data-theme', value: 'dark' },
  ];
  
  attributeChecks.forEach(({ element, attribute, value }) => {
    if (element.getAttribute(attribute) === value) {
      signals.push({
        type: 'dom-attribute',
        attribute: `${attribute}="${value}"`,
        element: element.tagName.toLowerCase(),
        confidence: 'high',
        implementation: 'custom'
      });
    }
  });
  
  return signals;
}

3. Class Names on html/body (HIGH – 85%+ Confidence)

Class-based toggling is extremely common, especially with Tailwind CSS.

Common Class Patterns

/* Tailwind CSS Pattern */
<html class="dark">
<html class="light">
<body class="dark">

/* Custom Implementations */
<html class="dark-mode">
<html class="light-mode">
<html class="dark-theme">
<html class="light-theme">
<html class="theme-dark">
<html class="theme-light">

Detection Algorithm

function checkClassNames() {
  const html = document.documentElement;
  const body = document.body;
  const signals = [];
  
  const classNames = [
    'dark', 'light',
    'dark-mode', 'light-mode',
    'dark-theme', 'light-theme',
    'theme-dark', 'theme-light'
  ];
  
  [html, body].forEach(element => {
    classNames.forEach(className => {
      if (element.classList.contains(className)) {
        signals.push({
          type: 'class-name',
          className,
          element: element.tagName.toLowerCase(),
          confidence: 'high',
          implementation: element === html ? 'tailwind-or-custom' : 'custom'
        });
      }
    });
  });
  
  return signals;
}

Tailwind CSS Specific Detection

// Detect Tailwind's dark: prefix classes
function checkTailwindDarkClasses() {
  const allElements = document.querySelectorAll('*');
  const tailwindDarkClasses = [];
  
  allElements.forEach(el => {
    const classes = el.classList;
    classes.forEach(cls => {
      if (cls.startsWith('dark:')) {
        tailwindDarkClasses.push(cls);
      }
    });
  });
  
  return {
    hasTailwindDark: tailwindDarkClasses.length > 0,
    classes: tailwindDarkClasses.slice(0, 10), // Sample
    confidence: tailwindDarkClasses.length > 0 ? 'very-high' : 'none'
  };
}

4. Library Signatures (VERY HIGH – 95%+ Confidence)

Popular dark mode libraries leave distinct signatures.

darkmode.js

// Detection Signatures
const darkmodeSignatures = {
  elements: ['.darkmode-layer', '.darkmode-toggle', '.darkmode--activated'],
  class: 'darkmode--activated',
  method: () => typeof Darkmode === 'function'
};

function checkDarkmodeJS() {
  const layer = document.querySelector('.darkmode-layer');
  const toggle = document.querySelector('.darkmode-toggle');
  const activated = document.body.classList.contains('darkmode--activated');
  
  return {
    detected: !!(layer || toggle || activated),
    confidence: layer && toggle ? 'very-high' : 'high',
    signatures: { layer: !!layer, toggle: !!toggle, activated }
  };
}

Dark Reader

// Detection Signatures
const darkReaderSignatures = {
  api: () => typeof window.DarkReader !== 'undefined',
  classes: '[class*="darkreader"]',
  filters: 'filter: invert()',
  styleTags: 'style[data-darkreader]'
};

function checkDarkReader() {
  const hasAPI = typeof window.DarkReader !== 'undefined';
  const hasClasses = document.querySelector('[class*="darkreader"]');
  const hasInlineFilters = Array.from(document.querySelectorAll('*'))
    .some(el => el.style.filter?.includes('invert'));
  
  return {
    detected: hasAPI || hasClasses || hasInlineFilters,
    confidence: hasAPI ? 'very-high' : hasClasses ? 'high' : 'medium',
    signatures: { api: hasAPI, classes: !!hasClasses, filters: hasInlineFilters }
  };
}

next-themes (Next.js)

// Detection Signatures
const nextThemesSignatures = {
  provider: 'ThemeProvider',
  hook: 'useTheme',
  localStorage: 'theme',
  htmlClass: 'dark',
  suppressHydrationWarning: true
};

function checkNextThemes() {
  // Check for next-themes patterns
  const html = document.documentElement;
  const hasClass = html.classList.contains('dark') || html.classList.contains('light');
  const hasStyle = html.style.colorScheme;
  const localStorageTheme = (() => {
    try {
      return localStorage.getItem('theme');
    } catch { return null; }
  })();
  
  return {
    detected: hasClass && localStorageTheme,
    confidence: hasClass && localStorageTheme === 'dark' ? 'very-high' : 'medium',
    currentTheme: localStorageTheme
  };
}

VueUse (Vue 3)

function checkVueUse() {
  try {
    const vueUseTheme = localStorage.getItem('vueuse-color-scheme');
    const html = document.documentElement;
    const hasDarkClass = html.classList.contains('dark');
    
    return {
      detected: vueUseTheme || hasDarkClass,
      confidence: vueUseTheme === 'dark' ? 'very-high' : 'medium',
      currentTheme: vueUseTheme
    };
  } catch {
    return { detected: false, confidence: 'none' };
  }
}

5. Toggle Buttons (HIGH – 80%+ Confidence)

Theme toggle buttons are strong indicators of dark mode capability.

Common Selectors

const toggleSelectors = [
  // Data attributes
  '[data-theme-toggle]',
  '[data-toggle-theme]',
  '[data-theme-switcher]',
  
  // Class names
  '.theme-toggle',
  '.theme-switch',
  '.theme-switcher',
  '.dark-mode-toggle',
  '.dark-mode-switch',
  '.mode-toggle',
  
  // Aria labels
  '[aria-label*="dark"]',
  '[aria-label*="light"]',
  '[aria-label*="theme"]',
  '[aria-label*="mode"]',
  
  // onclick handlers
  '[onclick*="theme"]',
  '[onclick*="dark"]',
  '[onclick*="toggle"]',
  
  // ID patterns
  'button[id*="theme"]',
  'button[id*="dark"]',
  'input[id*="theme"]',
  
  // Icon patterns (common in toggles)
  '.sun-icon',
  '.moon-icon',
  '[class*="sun"]',
  '[class*="moon"]'
];

function checkToggleButtons() {
  const signals = [];
  
  toggleSelectors.forEach(selector => {
    const elements = document.querySelectorAll(selector);
    if (elements.length > 0) {
      signals.push({
        type: 'toggle-button',
        selector,
        count: elements.length,
        confidence: selector.includes('data-') ? 'high' : 'medium'
      });
    }
  });
  
  return signals;
}

6. CSS Custom Properties (MEDIUM – 70%+ Confidence)

CSS variables with theme-related names.

function checkCSSCustomProperties() {
  const styles = getComputedStyle(document.documentElement);
  const signals = [];
  
  const themeProperties = [
    '--background', '--background-color', '--bg', '--bg-color',
    '--foreground', '--text-color', '--text', '--color-text',
    '--theme-bg', '--theme-text', '--theme-background',
    '--color-background', '--color-foreground',
    '--primary', '--secondary', '--accent',
    '--mui-palette-background-default',  // Material UI
    '--bs-body-bg', '--bs-body-color'    // Bootstrap
  ];
  
  themeProperties.forEach(prop => {
    const value = styles.getPropertyValue(prop).trim();
    if (value && isDarkColor(value)) {
      signals.push({
        type: 'css-custom-property',
        property: prop,
        value,
        confidence: 'medium'
      });
    }
  });
  
  return signals;
}

function isDarkColor(color) {
  // Parse RGB/HSL/Hex and determine if dark
  if (!color) return false;
  
  // Hex: #121212, #1a1a1a, etc.
  if (color.startsWith('#')) {
    const hex = color.slice(1);
    if (hex.length === 6) {
      const r = parseInt(hex.slice(0, 2), 16);
      const g = parseInt(hex.slice(2, 4), 16);
      const b = parseInt(hex.slice(4, 6), 16);
      // Luminance formula
      const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
      return luminance < 0.5;
    }
  }
  
  // RGB: rgb(18, 18, 18)
  if (color.startsWith('rgb')) {
    const match = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
    if (match) {
      const [, r, g, b] = match.map(Number);
      const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
      return luminance < 0.5;
    }
  }
  
  return false;
}

7. prefers-color-scheme Media Query (LOW – 40%+ Confidence)

CSS-only or system preference detection.

/* CSS Detection */
@media (prefers-color-scheme: dark) {
  :root {
    --background: #121212;
    --text: #ffffff;
  }
}
// JavaScript Detection
function checkPrefersColorScheme() {
  const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  const isLight = window.matchMedia('(prefers-color-scheme: light)').matches;
  
  return {
    systemPrefersDark: isDark,
    systemPrefersLight: isLight,
    hasMediaQuerySupport: isDark !== undefined,
    confidence: 'low'  // Alone, doesn't prove site has dark mode
  };
}

// Check CSS for media queries
function checkCSSForMediaQueries() {
  const stylesheets = document.styleSheets;
  const signals = [];
  
  try {
    for (const sheet of stylesheets) {
      for (const rule of sheet.cssRules) {
        if (rule instanceof CSSMediaRule) {
          if (rule.media.mediaText.includes('prefers-color-scheme')) {
            signals.push({
              type: 'media-query',
              media: rule.media.mediaText,
              confidence: 'medium'
            });
          }
        }
      }
    }
  } catch (e) {
    // Cross-origin stylesheet
  }
  
  return signals;
}

8. color-scheme CSS Property (MEDIUM – 70%+ Confidence)

Modern CSS property for theme indication.

:root {
  color-scheme: light dark;
}

[data-theme="dark"] {
  color-scheme: dark;
}
function checkColorSchemeProperty() {
  const html = document.documentElement;
  const style = getComputedStyle(html);
  
  return {
    colorScheme: style.colorScheme,
    hasDarkSupport: style.colorScheme?.includes('dark'),
    inlineStyle: html.style.colorScheme,
    confidence: style.colorScheme?.includes('dark') ? 'medium' : 'low'
  };
}

Complete Detection Algorithm

DarkModeDetector Class

class DarkModeDetector {
  constructor() {
    this.signals = [];
    this.observer = null;
  }

  detect() {
    this.signals = [];
    
    // Run all detection methods
    this.checkLocalStorage();
    this.checkDOMAttributes();
    this.checkClassNames();
    this.checkLibrarySignatures();
    this.checkToggleButtons();
    this.checkCSSCustomProperties();
    this.checkPrefersColorScheme();
    this.checkColorSchemeProperty();
    this.checkTailwindDarkClasses();
    
    return {
      hasDarkMode: this.signals.length > 0,
      confidence: this.calculateConfidence(),
      currentTheme: this.getCurrentTheme(),
      implementation: this.detectImplementationType(),
      signals: this.signals,
      summary: this.generateSummary()
    };
  }

  checkLocalStorage() {
    const keys = ['theme', 'darkMode', 'dark-mode', 'color-scheme', 'vueuse-color-scheme'];
    
    keys.forEach(key => {
      try {
        const value = localStorage.getItem(key);
        if (value === 'dark' || value === 'enabled' || value === 'true') {
          this.signals.push({
            type: 'localStorage',
            key,
            value,
            confidence: 'very-high',
            weight: 4
          });
        }
      } catch (e) {}
    });
  }

  checkDOMAttributes() {
    const html = document.documentElement;
    const body = document.body;
    
    const checks = [
      { element: html, attribute: 'data-theme', value: 'dark' },
      { element: html, attribute: 'data-bs-theme', value: 'dark' },
      { element: html, attribute: 'data-mui-color-scheme', value: 'dark' },
      { element: html, attribute: 'data-mode', value: 'dark' },
      { element: html, attribute: 'color-mode', value: 'dark' },
      { element: body, attribute: 'data-theme', value: 'dark' },
    ];
    
    checks.forEach(({ element, attribute, value }) => {
      if (element.getAttribute(attribute) === value) {
        this.signals.push({
          type: 'dom-attribute',
          attribute: `${attribute}="${value}"`,
          element: element.tagName.toLowerCase(),
          confidence: 'high',
          weight: 3
        });
      }
    });
  }

  checkClassNames() {
    const html = document.documentElement;
    const body = document.body;
    const classNames = ['dark', 'dark-mode', 'dark-theme', 'theme-dark'];
    
    [html, body].forEach(element => {
      classNames.forEach(className => {
        if (element.classList.contains(className)) {
          this.signals.push({
            type: 'class-name',
            className,
            element: element.tagName.toLowerCase(),
            confidence: 'high',
            weight: 3
          });
        }
      });
    });
  }

  checkLibrarySignatures() {
    // darkmode.js
    if (document.querySelector('.darkmode-layer, .darkmode-toggle')) {
      this.signals.push({
        type: 'library',
        name: 'darkmode.js',
        confidence: 'very-high',
        weight: 4
      });
    }
    
    // Dark Reader
    if (window.DarkReader || document.querySelector('[class*="darkreader"]')) {
      this.signals.push({
        type: 'library',
        name: 'darkreader',
        confidence: 'very-high',
        weight: 4
      });
    }
  }

  checkToggleButtons() {
    const selectors = [
      '[data-theme-toggle]',
      '.theme-toggle',
      '.dark-mode-toggle',
      '[aria-label*="theme"]'
    ];
    
    selectors.forEach(selector => {
      if (document.querySelector(selector)) {
        this.signals.push({
          type: 'toggle-button',
          selector,
          confidence: 'high',
          weight: 3
        });
      }
    });
  }

  checkCSSCustomProperties() {
    const styles = getComputedStyle(document.documentElement);
    const bg = styles.getPropertyValue('--background').trim();
    
    if (bg && this.isDarkColor(bg)) {
      this.signals.push({
        type: 'css-custom-property',
        property: '--background',
        value: bg,
        confidence: 'medium',
        weight: 2
      });
    }
  }

  checkPrefersColorScheme() {
    const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    // Low confidence alone, but useful context
    this.signals.push({
      type: 'system-preference',
      prefersDark: isDark,
      confidence: 'low',
      weight: 1
    });
  }

  checkColorSchemeProperty() {
    const style = getComputedStyle(document.documentElement);
    if (style.colorScheme?.includes('dark')) {
      this.signals.push({
        type: 'color-scheme-property',
        value: style.colorScheme,
        confidence: 'medium',
        weight: 2
      });
    }
  }

  checkTailwindDarkClasses() {
    const allElements = document.querySelectorAll('*');
    let hasDarkClasses = false;
    
    allElements.forEach(el => {
      el.classList.forEach(cls => {
        if (cls.startsWith('dark:')) hasDarkClasses = true;
      });
    });
    
    if (hasDarkClasses) {
      this.signals.push({
        type: 'tailwind-dark-classes',
        confidence: 'very-high',
        weight: 4
      });
    }
  }

  isDarkColor(color) {
    if (!color) return false;
    
    if (color.startsWith('#')) {
      const hex = color.slice(1);
      if (hex.length === 6) {
        const r = parseInt(hex.slice(0, 2), 16);
        const g = parseInt(hex.slice(2, 4), 16);
        const b = parseInt(hex.slice(4, 6), 16);
        const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
        return luminance < 0.5;
      }
    }
    
    if (color.startsWith('rgb')) {
      const match = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
      if (match) {
        const [, r, g, b] = match.map(Number);
        const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
        return luminance < 0.5;
      }
    }
    
    return false;
  }

  calculateConfidence() {
    const weights = { 'very-high': 4, 'high': 3, 'medium': 2, 'low': 1 };
    const totalWeight = this.signals.reduce((sum, s) => sum + (s.weight || 0), 0);
    
    if (totalWeight >= 12) return 'very-high';
    if (totalWeight >= 8) return 'high';
    if (totalWeight >= 4) return 'medium';
    return 'low';
  }

  getCurrentTheme() {
    // Check localStorage first
    const themeKeys = ['theme', 'darkMode', 'color-scheme'];
    for (const key of themeKeys) {
      try {
        const value = localStorage.getItem(key);
        if (value === 'dark' || value === 'light') {
          return value;
        }
      } catch (e) {}
    }
    
    // Check DOM attributes
    const html = document.documentElement;
    if (html.getAttribute('data-theme')) return html.getAttribute('data-theme');
    if (html.classList.contains('dark')) return 'dark';
    if (html.classList.contains('light')) return 'light';
    
    return 'unknown';
  }

  detectImplementationType() {
    const types = new Set();
    
    this.signals.forEach(signal => {
      if (signal.type === 'localStorage') types.add('javascript');
      if (signal.type === 'dom-attribute') types.add('custom');
      if (signal.type === 'class-name') types.add('tailwind-or-custom');
      if (signal.type === 'library') types.add(signal.name);
      if (signal.type === 'tailwind-dark-classes') types.add('tailwind');
    });
    
    return Array.from(types);
  }

  generateSummary() {
    return {
      totalSignals: this.signals.length,
      criticalSignals: this.signals.filter(s => s.confidence === 'very-high').length,
      highSignals: this.signals.filter(s => s.confidence === 'high').length,
      implementationTypes: this.detectImplementationType(),
      detectedLibraries: this.signals
        .filter(s => s.type === 'library')
        .map(s => s.name)
    };
  }

  // Setup MutationObserver for dynamic changes
  setupObserver(callback) {
    const targets = [
      document.documentElement,
      document.body,
      document.getElementById('root'),
      document.getElementById('app'),
      document.getElementById('__next')
    ];
    
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'attributes') {
          if (['class', 'data-theme', 'data-mode', 'color-mode'].includes(mutation.attributeName)) {
            callback({
              type: 'theme-change',
              attribute: mutation.attributeName,
              oldValue: mutation.oldValue,
              newValue: mutation.target.getAttribute(mutation.attributeName)
            });
          }
        }
      });
    });
    
    targets.forEach(target => {
      if (target) {
        this.observer.observe(target, {
          attributes: true,
          attributeFilter: ['class', 'data-theme', 'data-mode', 'color-mode', 'style']
        });
      }
    });
    
    return () => this.observer.disconnect();
  }
}

// Usage Example
const detector = new DarkModeDetector();
const result = detector.detect();
console.log('Dark Mode Detection Result:', result);

// Setup live monitoring
const unobserve = detector.setupObserver((change) => {
  console.log('Theme changed:', change);
  const newResult = detector.detect();
  console.log('Updated detection:', newResult);
});

Edge Cases

1. Cross-Origin iframes

// Cannot access localStorage/CSS from cross-origin iframes
try {
  const iframeStorage = iframe.contentWindow.localStorage;
} catch (e) {
  // Cross-origin restriction
  console.log('Cannot access iframe storage');
}

2. Shadow DOM

// Need to query Shadow DOM separately
function checkShadowDOM(root) {
  const shadowRoots = root.querySelectorAll('*').map(el => el.shadowRoot).filter(Boolean);
  
  shadowRoots.forEach(shadow => {
    const styles = getComputedStyle(shadow);
    // Check shadow DOM styles
  });
}

3. Server-Side Rendering (SSR)

// SSR may render initial theme without JavaScript
// Check for hydration markers
const hasHydration = document.querySelector('[data-hydration]') || 
                     document.querySelector('[data-reactroot]');

4. Multiple Theme Systems

// Some sites use both system preference AND manual toggle
// Detect both and report accurately
const hasSystemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches;
const hasManualToggle = localStorage.getItem('theme') !== null;

Output Format

Detection Result Schema

interface DetectionResult {
  hasDarkMode: boolean;
  confidence: 'very-high' | 'high' | 'medium' | 'low';
  currentTheme: 'dark' | 'light' | 'unknown';
  implementation: string[];  // ['javascript', 'tailwind', 'darkmode.js']
  signals: Signal[];
  summary: {
    totalSignals: number;
    criticalSignals: number;
    highSignals: number;
    implementationTypes: string[];
    detectedLibraries: string[];
  };
}

interface Signal {
  type: 'localStorage' | 'dom-attribute' | 'class-name' | 'library' | 
        'toggle-button' | 'css-custom-property' | 'system-preference' |
        'color-scheme-property' | 'tailwind-dark-classes';
  confidence: 'very-high' | 'high' | 'medium' | 'low';
  weight: number;
  [key: string]: any;  // Additional signal-specific data
}

Example Output

{
  "hasDarkMode": true,
  "confidence": "very-high",
  "currentTheme": "dark",
  "implementation": ["javascript", "tailwind"],
  "signals": [
    {
      "type": "localStorage",
      "key": "theme",
      "value": "dark",
      "confidence": "very-high",
      "weight": 4
    },
    {
      "type": "class-name",
      "className": "dark",
      "element": "html",
      "confidence": "high",
      "weight": 3
    },
    {
      "type": "tailwind-dark-classes",
      "confidence": "very-high",
      "weight": 4
    }
  ],
  "summary": {
    "totalSignals": 3,
    "criticalSignals": 2,
    "highSignals": 1,
    "implementationTypes": ["javascript", "tailwind"],
    "detectedLibraries": []
  }
}

Quick Reference

Detection Method Confidence Implementation Code Pattern
localStorage Very High JavaScript getItem('theme') === 'dark'
data-theme High Custom getAttribute('data-theme')
.dark class High Tailwind/Custom classList.contains('dark')
Library signatures Very High Library .darkmode-layer, DarkReader
Toggle buttons High JavaScript [data-theme-toggle]
CSS variables Medium CSS --background: #121212
color-scheme Medium CSS color-scheme: dark
prefers-color-scheme Low CSS/JS @media (prefers-color-scheme: dark)

Resources


Dark Mode Detection Skill v1.0.0 – For browser extension development and website analysis