# Theme Toggle

<FrameworkCodeSwitcher>
  <div slot="astro">
```astro
---
import { ThemeToggle } from "@/components/starwind/theme-toggle";
---

<ThemeToggle ariaLabel="Toggle theme" />
```
  </div>
  <div slot="react">
```tsx
import { ThemeToggle } from "@/components/starwind/theme-toggle";

export function Example() {
  return (
    <>
      <ThemeToggle ariaLabel="Toggle theme" />
    </>
  );
}
```
  </div>
  <div slot="vue">
```vue
<script setup lang="ts">
import { ThemeToggle } from "@/components/starwind/theme-toggle";
</script>

<template>
  <ThemeToggle ariaLabel="Toggle theme" />
</template>
```
  </div>
</FrameworkCodeSwitcher>

> **Info:** Theme Toggle now uses the shared theme controller from `@starwind-ui/astro/theme`. Controls with
the same sync group stay aligned across navigation and system-theme changes.

## Installation

```bash
npx starwind@latest add theme-toggle --framework astro
```
</DocsTabsContent>
```bash
npx starwind@latest add theme-toggle --framework react
```
</DocsTabsContent>
```bash
npx starwind@latest add theme-toggle --framework vue
```
</DocsTabsContent>
</DocsTabs>

## Usage

<FrameworkCodeSwitcher>
  <div slot="astro">
```astro
---
import { ThemeToggle } from "@/components/starwind/theme-toggle";
---

<ThemeToggle ariaLabel="Toggle theme" />
```
  </div>
  <div slot="react">
```tsx
import { ThemeToggle } from "@/components/starwind/theme-toggle";

export function Example() {
  return (
    <>
      <ThemeToggle ariaLabel="Toggle theme" />
    </>
  );
}
```
  </div>
  <div slot="vue">
```vue
<script setup lang="ts">
import { ThemeToggle } from "@/components/starwind/theme-toggle";
</script>

<template>
  <ThemeToggle ariaLabel="Toggle theme" />
</template>
```
  </div>
</FrameworkCodeSwitcher>

## Preventing a theme flash

Render `ThemeInitScript` in your document head. It applies the persisted or system theme before
the page becomes visible.

<FrameworkCodeSwitcher>
<div slot="astro">
```astro
---
import { ThemeInitScript } from "@starwind-ui/astro/theme";
---

<html>
  <head>
    <ThemeInitScript />
  </head>
  <body>
    <slot />
  </body>
</html>
```
</div>
<div slot="react">
```tsx
import type { ReactNode } from "react";
import { getThemeInitScript } from "@starwind-ui/react/theme";

function ThemeInitScript() {
  return <script dangerouslySetInnerHTML={{ __html: getThemeInitScript() }} />;
}

export function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html suppressHydrationWarning>
      <head>
        <ThemeInitScript />
      </head>
      <body>{children}</body>
    </html>
  );
}
```
</div>
<div slot="vue">
```vue
<script setup lang="ts">
import { defineComponent, h } from "vue";
import { getThemeInitScript } from "@starwind-ui/vue/theme";

// Render this component in your server-rendered document head.
const ThemeInitScript = defineComponent(() => {
  return () => h("script", { innerHTML: getThemeInitScript() });
});
</script>

<template>
  <ThemeInitScript />
</template>
```
</div>
</FrameworkCodeSwitcher>

## Custom icons

<FrameworkCodeSwitcher>
  <div slot="astro">
```astro
---
import Heart from "@tabler/icons/outline/heart.svg";
import Star from "@tabler/icons/outline/star.svg";
import { ThemeToggle } from "@/components/starwind/theme-toggle";
---

<ThemeToggle ariaLabel="Toggle theme with custom icons">
  <Heart
    slot="light-icon"
    class="hidden size-5 group-data-[state=off]:data-ready:block"
    data-theme-icon
  />
  <Star
    slot="dark-icon"
    class="hidden size-5 group-data-[state=on]:data-ready:block"
    data-theme-icon
  />
</ThemeToggle>
```
  </div>
  <div slot="react">
```tsx
import { IconHeart as Heart, IconStar as Star } from "@tabler/icons-react";
import { ThemeToggle } from "@/components/starwind/theme-toggle";

export function Example() {
  return (
    <ThemeToggle
      ariaLabel="Toggle theme with custom icons"
      lightIcon={
        <Heart
          className="hidden size-5 group-data-[state=off]:data-ready:block"
          data-theme-icon
        />
      }
      darkIcon={
        <Star
          className="hidden size-5 group-data-[state=on]:data-ready:block"
          data-theme-icon
        />
      }
    />
  );
}
```
  </div>
  <div slot="vue">
```vue
<script setup lang="ts">
import { IconHeart as Heart } from "@tabler/icons-vue";
import { IconStar as Star } from "@tabler/icons-vue";
import { ThemeToggle } from "@/components/starwind/theme-toggle";
</script>

<template>
  <ThemeToggle ariaLabel="Toggle theme with custom icons">
    <template #light-icon
      ><Heart class="hidden size-5 group-data-[state=off]:data-ready:block" data-theme-icon
    /></template>
    <template #dark-icon
      ><Star class="hidden size-5 group-data-[state=on]:data-ready:block" data-theme-icon
    /></template>
  </ThemeToggle>
</template>
```
  </div>
</FrameworkCodeSwitcher>

> **Info:** Custom icon visibility follows the Runtime-owned `data-state` and `data-ready` attributes. Keep
`data-theme-icon` on each icon so the controller can synchronize it.

## API Reference
### ThemeToggle
| Prop | Type | Required | Default | Kind | Description |
| --- | --- | --- | --- | --- | --- |
| `ariaLabel` | `string` | No | `"Toggle theme"` | Wrapper prop | Provides an accessible label when visible text is not available. |
| `defaultPressed` | `boolean` | No | - | Wrapper prop | Sets the initial pressed state when the control is uncontrolled. |
| `disabled` | `boolean` | No | `false` | Wrapper prop | Disables interaction with the component. |
| `pressed` | `boolean` | No | - | Wrapper prop | Controls the pressed state. |
| `size` | `"sm" \| "md" \| "lg"` | No | `"md"` | Styled variant | Selects the component's visual size. |
| `syncGroup` | `string` | No | `"starwind-theme"` | Wrapper prop | Synchronizes pressed state across Theme Toggles in the same named group. |
| `value` | `string` | No | - | Wrapper prop | Controls or identifies the component value. |
| `variant` | `"default" \| "outline"` | No | `"outline"` | Styled variant | Selects the component's visual variant. |
- Inherits button attributes. Omits `aria-pressed`, `defaultPressed`, `disabled`, `onChange`, `type`, and `value`.

## Changelog

### v2.0.0

- Added contract-generated Astro and React implementations and replaced page-local storage logic with the shared Runtime theme controller and `ThemeInitScript`.
- See the [Runtime theme reference](/docs/runtime/#theme) for the shared theme behavior and helpers.
- Theme Toggle now uses the shared theme controller from `@starwind-ui/astro/theme`. Controls with
the same sync group stay aligned across Astro navigation and system-theme changes.