Skip to Content
Hashti Design System
Getting Started

Theming & Layering

Master Hashti's 8 design themes, nested contextual Layering system, and dark mode toggles.

Theming & Layering

Hashti features a multi-tiered theming engine designed around disciplined spatial logic and Frame’s refined modern aesthetic.

Themes are applied via CSS custom properties scoped to the data-hashti-theme attribute or handled automatically using the <Theme> and <Layer> React components.


The 8 Supported Themes

Hashti provides 8 first-class themes divided into three aesthetic families:

Theme FamilyTheme IdentifierModeDescription
Hashti CorewhiteLightPristine high-contrast white theme for document-heavy workspaces.
g10LightSoft warm grey (Gray 10) reducing eye strain for long analytical sessions.
g90DarkCharcoal dark grey (Gray 90) balanced for low-light developer workflows.
g100DarkDeep obsidian black (Gray 100) high-contrast dark environment.
Relayrelay-lightLightPurpose-built for operational command hubs and developer tooling.
relay-darkDarkTactical high-density dark UI for status monitoring and logs.
Frame (Recommended)frame-lightLightHashti’s default aesthetic: crisp borders, calm backgrounds, 10px control radiuses, and Persian architectural serenity.
frame-darkDarkDeep ink surface with refined translucent layers and luminous turquoise accents.

Using the <Theme> Component

The <Theme> component wraps any subtree to set or switch the active theme. It injects data-hashti-theme and configures React Aria’s I18nProvider automatically.

import { Theme, Button } from "@hashti/react";

export function CustomSection() {
  return (
    <Theme theme="frame-dark">
      <div style={{ padding: "24px" }}>
        <h3>Dark Frame Section</h3>
        <Button variant="primary">Luminous Action</Button>
      </div>
    </Theme>
  );
}

Direct HTML Attribute Usage

If you prefer applying themes directly in HTML or server-rendered layouts without React context:

<!-- Light theme root -->
<html lang="en" data-hashti-theme="frame-light">
  <body>
    ...
    <!-- Local dark theme island -->
    <div data-hashti-theme="frame-dark" class="hashti-theme">...</div>
  </body>
</html>

Nested Layering System (<Layer>)

In Persian architecture, stepping through the Hashti reveals successive inner spaces (from vestibule to corridor to central court). Similarly, modern enterprise interfaces require visual elevation when components are nested inside containers (e.g. cards placed on a sidebar, or inputs placed inside a modal card).

Rather than hardcoding arbitrary shadow levels or color overrides, Hashti uses a mathematical 3-tier Layering System:

  • Layer 0 (data-hashti-layer="0"): The background canvas (e.g. the main page body).
  • Layer 1 (data-hashti-layer="1"): Primary raised surfaces (sidebars, data tables, main content cards).
  • Layer 2 (data-hashti-layer="2"): Nested elements inside containers (cards inside sidebars, input fields inside cards, floating popovers).

Contextual Layer Nesting

Each <Layer> component automatically increments the layer depth of its children:

import { Theme, Layer, Card, TextField, Button } from "@hashti/react";

export function LayeredDashboard() {
  return (
    <Theme theme="frame-light">
      {/* Layer 0: Page Canvas */}
      <div style={{ background: "var(--hashti-background)", padding: "32px" }}>
        <h1>Page Canvas (Layer 0)</h1>

        {/* Layer 1: Container / Sidebar */}
        <Layer>
          <div
            style={{ background: "var(--hashti-layer-01)", padding: "24px", borderRadius: "8px" }}
          >
            <h2>Sidebar Panel (Layer 1)</h2>

            {/* Layer 2: Nested Card */}
            <Layer>
              <Card style={{ background: "var(--hashti-layer-02)", padding: "16px" }}>
                <h3>Inspector Card (Layer 2)</h3>
                <TextField label="Resource Identifier" placeholder="res-8492" />
                <Button variant="primary" style={{ marginTop: "12px" }}>
                  Update
                </Button>
              </Card>
            </Layer>
          </div>
        </Layer>
      </div>
    </Theme>
  );
}

Building a Dark Mode Toggle

Build a dark and light mode toggle with React state or local storage:

import { useState } from "react";
import { Theme, Button, type ThemeName } from "@hashti/react";

export function ThemeSwitcherApp({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<ThemeName>("frame-light");

  const toggleTheme = () => {
    setTheme((prev) => (prev === "frame-light" ? "frame-dark" : "frame-light"));
  };

  return (
    <Theme theme={theme}>
      <header
        style={{
          padding: "16px",
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
        }}
      >
        <span>
          Current Theme: <strong>{theme}</strong>
        </span>
        <Button variant="secondary" onPress={toggleTheme}>
          Switch to {theme === "frame-light" ? "Dark Mode (🌙)" : "Light Mode (☀️)"}
        </Button>
      </header>
      <main style={{ padding: "24px" }}>{children}</main>
    </Theme>
  );
}

Accessing Theme Context: useTheme

Use the useTheme hook inside any component to read the current active theme, layer depth, and text direction:

import { useTheme } from "@hashti/react";

export function ThemeStatusBadge() {
  const { theme, layer, direction } = useTheme();

  return (
    <div style={{ fontSize: "12px", fontFamily: "var(--hashti-font-mono)" }}>
      Active: {theme} | Depth: L{layer} | Direction: {direction.toUpperCase()}
    </div>
  );
}

[!TIP] Portals and Overlays: Overlays like Modals and Tooltips render outside the main DOM tree. Hashti components automatically use useOverlayTheme() to forward data-hashti-theme and data-hashti-layer to portal roots, ensuring colors and typography remain strictly consistent.