An open API service indexing awesome lists of open source software.

https://github.com/lc-2025/theme-switcher

A theme style selector
https://github.com/lc-2025/theme-switcher

dark-mode github-actions headlessui jest-testing light-mode npm-packages open-source react-component tailwind-css theme-swicther typescript ui-library

Last synced: 2 months ago
JSON representation

A theme style selector

Awesome Lists containing this project

README

          

# Theme Switcher

A theme style selector.

![Theme Switcher](./docs/preview.gif 'Theme Switcher')
[![Theme Switcher CI](https://github.com/lc-2025/theme-switcher/actions/workflows/ci.yml/badge.svg)](https://github.com/lc-2025/theme-switcher/actions/workflows/ci.yml) [![Theme Switcher CD](https://github.com/lc-2025/theme-switcher/actions/workflows/cd.yml/badge.svg)](https://github.com/lc-2025/theme-switcher/actions/workflows/cd.yml)

## About

_Theme Switcher_ is a stateful _React_ component based on _TailwindCSS_ and _HeadlessUI_ agnostic library for Light and Dark theme management.
A totally customizable and adaptable ready-to-go utility provided as a _NPM_ package to speed up the workflow.

## Features

- Personalizable layout and template
- Theme state and storage management
- Evironmental auto-detection (system & browser-based)

## Stack

- **Languages**: JavaScript, Typescript, YAML, Bash
- **Environments**: DOM
- **Libraries**: Storage Manager, HeadlessUI, Testing Library
- **Frameworks**: React, TailwindCSS, Jest
- **Linters/Plugins**: ESLint, Prettier
- **Compilers**: Babel, TypeScript
- **Bundlers**: Rollup
- **Testing**: Jest, Testing Library
- **Versioning**: GitHub, Husky
- **Continuous-Integration/Delivery**: GitHub Actions
- **Deployment**: NPM Registry

## Getting Started

The package production version is available on _NPM_ at [https://npmjs.com/package/@lc-2025/theme-switcher](https://npmjs.com/package/@lc-2025/theme-switcher).
For any contribution, maintanance and/or trial needs, please refer to the following specifications.

### Requirements

_Theme Switcher_ is based on _React_, _TailwindCSS_ and _HeadlessUI_ libraries to provide its features.
Please be sure to include these peer dependencies in your setup process.

### Installation

On terminal, from project root:

```bash
# Assuming React already installed

npm i @lc-2025/theme-switcher tailwindcss @headlessui/react
```

### Configuration

- Update the _TailwindCSS_ configuration:

```js
// tailwind.config.{js|ts}

const config = {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
// Include Theme Switcher classes as well
'./node_modules/@lc-2025/theme-switcher/**/*.js',
],
// Enable Dark-Mode support via class
darkMode: 'class',
};

export default config;
```

- Update the global CSS file:

```css
/* i.e. {global|index}.css */

/* Import TailwindCSS */
@import 'tailwindcss';

/**
* Register Theme Switcher as source
* Please note that the path is relative to file location, i.e.
* - /src/style.css -> @source "../node_modules/...
*/
@source "../node_modules/@lc-2025/theme-switcher/dist";

/* Override the default configuration */
@custom-variant dark (&:where(.dark, .dark *));

/* ... */
```

- Update the Root component:

```jsx
// i.e. main.{jsx|tsx}

// ...

createRoot(document.getElementById('root')!).render(

{/* Include the Theme Switcher Context provider */}



,
)
```

### Usage

- Define your CSS variables/rules:

```css
/* i.e. {global|index}.css */

/* ... */

@theme {
--black: #000;
--white: #fff;
--color-primary: var(--black);
--color-accent: var(--white);
}

/* Custom Dark-Mode rules */
.dark {
--color-primary: var(--white);
--color-accent: var(--black);

background-color: var(--color-accent);
color: var(--color-primary);
}

/* ... */
```

- Include _Theme Switcher_ in your App (basic usage):

```jsx
// MyComponent.{jsx|tsx}

import { useEffect } from 'react';
import { ThemeSwitcher, useThemeContext } from '@lc-2025/theme-switcher';

const MyComponent = () => {
// Styles definition
const style = {
// Pass class names to style properties
iconDark: 'my-dark-icon',
iconLight: 'my-light-icon',
};
// i.e. Check selected theme
const theme = useThemeContext();

useEffect(() => {
console.log(theme);
});

/**
* Icons
* May be any valid JSX/TSX element, i.e.:
* - Icon library nodes
* - Custom components
* - Native DOM nodes
* - Etc.
*/
const iconDark = () => <>foo>;

// ...

return (
//...
bar}
style={style}
/>
//...
);
};
```

#### Advanced usage

- With _TailwindCSS_:

```jsx
// MyComponent.{jsx|tsx}

// ...

/**
* Styles definition
* Theme Switcher is built upon a default style as fallback
* Declare TailwindCSS classes or your custom ones to
* change the component layout/template.
* Default approach: TailwindCSS classes
*/
const styleOverride = {
container: 'flex items-center mt-8',
field: 'h-8 w-14 bg-blue-300 data-checked:bg-blue-300 data-focus:outline-blue-300',
iconLight: 'font-bold mr-4 uppercase',
iconDark: 'font-bold ml-4 uppercase',
switch: 'bg-blue-600 group-data-[checked]:bg-blue-600 group-data-checked:translate-x-6 size-6'
};

// ...

return (
//...
{/* Styles override */}
Dark>}
iconLight={<>Light>}
style={styleOverride}
/>
//...
);
```

- With Custom classes:

```css
/* MyComponent.css */

.my-container {
align-items: flex-start;
display: flex;
flex-direction: column;
margin: 2rem 0 0;
text-align: center;

.my-field {
height: 3rem;
margin: 0.5rem 0;
width: 6rem;

&.my-field--dark {
border: 2px dotted var(--color-primary);
}

&.my-field--light {
border: 1px solid var(--color-primary);
}
}

.my-icon-dark {
align-self: flex-end;

&.my-icon-dark--dark {
font-weight: bold;
}

&.my-icon-dark--light {
font-weight: normal;
}
}

.my-icon-light {
font-style: italic;
}
}
```

```jsx
// MyComponent.{jsx|tsx}

/**
* Assuming MyComponent.{css|jsx|tsx} under:
* - /css/MyComponent.css
* - /components/MyComponent.{jsx|tsx}
*/
import '../css/MyComponent.css';

// ...

const styleCustom = {
// Total custom
container: 'my-container',
field: {
// Or mixed (Tailwind/Custom)
dark: 'my-field my-field--dark bg-red-300 data-checked:bg-red-300 data-focus:outline-red-300',
light: 'my-field my-field--light bg-yellow-300 data-checked:bg-yellow-300 data-focus:outline-yellow-300'
},
iconDark: {
dark: 'my-icon-dark my-icon-dark--dark',
light: 'my-icon-dark my-icon-dark--light'
},
iconLight: {
light: 'my-icon-light'
},
};

// ...

return (
//...
{/* Custom styles */}
Dark>}
iconLight={<>Light>}
style={styleCustom}
/>
//...
);
```

- With default classes:

```css
/* i.e. {global|index|MyComponent}.css */

/* Default CSS classes reference */

/* Container */
.theme-switcher {
/* ... */

/* Switch */
.theme-switcher__field {
/* ... */

/* Slider */
.field__switch {
/* ... */
}
}

/* Icon - Base */
.theme-switcher__icon {
/* ... */
}

/* Dark variant */
.theme-switcher__icon--dark {
/* ... */
}

/* Light variant */
.theme-switcher__icon--light {
/* ... */
}
}

/* ... */
```

```jsx
// MyComponent.{jsx|tsx}

/**
* With custom CSS file only
*
* Assuming MyComponent.{css|jsx|tsx} under:
* - /css/MyComponent.css
* - /components/MyComponent.{jsx|tsx}
*/
import '../css/MyComponent.css';

// ...

return (
//...
foo} iconLight={bar} />
//...
);
```

- Icon variants:

```jsx
// MyComponent.{jsx|tsx}

// ...

const theme = useThemeContext();

// ...

return (
//...
{/* Different icons according to the current theme */}
{'>8-|'}>,
light: <>{'D:-)'}>
}}
iconLight={
// Conditional rendering
theme === 'light' ?

Light

:
<>☀️>
}
/>
//...
);
```

### API

#### Props

- `iconDark`

```ts
ReactNode | { darkIcon?: ReactNode, lightIcon?: ReactNode }
```

Defines the dark theme icon or its variants

- `iconLight`

```ts
ReactNode | { darkIcon?: ReactNode, lightIcon?: ReactNode }
```

Defines the light theme icon or its variants

- `style` (optional)

[See below](#style)

Defines the component CSS classes

#### Style

- `container` (optional)

```ts
string;
```

Defines the component container

- `field` (optional)

```ts
{ dark?: ReactNode, light?: ReactNode }
```

Defines the component `Switch` child (slider backgorund) variants

- `iconDark` (optional)

```ts
{ dark?: ReactNode, light?: ReactNode }
```

Defines the component dark icon variants

- `iconLight` (optional)

```ts
{ dark?: ReactNode, light?: ReactNode }
```

Defines the component light icon variants

- `switch` (optional)

```ts
{ dark?: ReactNode, light?: ReactNode }
```

Defines the component slider child variants

## Contributing

### Setting Up

On terminal, from project root:

- To install dependencies

```bash
npm run install
```

- To lint the sources

```bash
npm run lint
```

- To build the production version

```bash
npm run build
```

### Tests

#### Unit

On terminal, from project root:

- To run the unit tests in `development` mode

```bash
npm run test
```

- To run the tests in `testing` mode (staging or content-integration/delivery environments)

```bash
npm run test:ci
```

### Deploy

_Theme Switcher_ is integrated and delivered to production via _GitHub Actions_ workflows pipeline, where the package is being set up, tested and built.
Then the artifacts are deployed on _NPM_ registry available at [https://npmjs.com/package/@lc-2025/theme-switcher](https://npmjs.com/package/@lc-2025/theme-switcher).

- To deploy the production version

```bash
npm run deploy
```

Please read more about required best practices on the specific [contributing reference document](./.github/CONTRIBUTING.md).