Guides/

Theming

Comprehensive design token system with four built-in themes: Dark, Light, Oatmeal, and Glass.

Built-in Themes

Hyena includes four complete themes. Each theme provides a full set of color, spacing, and effect tokens.

Dark Theme

Premium dark theme optimized for OLED displays. Deep blacks (#050505), high contrast white text, vibrant accents. Default theme.

Light Theme

Clean, bright theme for daytime use. Pure white background (#ffffff), dark text, softer shadows.

Oatmeal Theme

Warm, neutral theme with cream and beige tones (#F5F2ED). Earthy palette with reduced blue light and muted accents.

Glass Theme

Glassmorphism theme with translucent surfaces (rgba(255,255,255,0.65)), backdrop blur effects, and subtle shadows.


ThemeProvider

Wrap your app with ThemeProvider to enable theming:

import { ThemeProvider } from '@hyena-studio/react-native'

function App() {
  return (
    <ThemeProvider defaultTheme="dark">
      <MyApp />
    </ThemeProvider>
  )
}

Props

PropTypeDefaultDescription
defaultTheme'light' | 'dark' | 'oatmeal' | 'glass''dark'Initial theme
childrenReactNodeApp content

useTheme Hook

Access theme context in any component:

import { useTheme } from '@hyena-studio/react-native'

function MyComponent() {
  const {
    themeName,        // Current theme: 'dark' | 'light' | 'oatmeal' | 'glass'
    theme,            // Full theme object with all tokens
    tokens,           // Alias for theme
    setTheme,         // Function to change theme
    availableThemes,  // Array of available theme names
    isGlass,          // Helper boolean: true when themeName === 'glass'
  } = useTheme()

  return (
    <View style={{ backgroundColor: theme.colors.background }}>
      <Text style={{ color: theme.colors.text.primary }}>
        Current theme: {themeName}
      </Text>
      {isGlass && <Text>Glass mode active!</Text>}
      <Button onPress={() => setTheme('light')}>
        Switch to Light
      </Button>
    </View>
  )
}

Theme Toggle Example

function ThemeToggle() {
  const { themeName, setTheme } = useTheme()

  return (
    <View style={{ flexDirection: 'row', gap: 8 }}>
      <Button
        variant={themeName === 'dark' ? 'primary' : 'ghost'}
        onPress={() => setTheme('dark')}
      >
        Dark
      </Button>
      <Button
        variant={themeName === 'light' ? 'primary' : 'ghost'}
        onPress={() => setTheme('light')}
      >
        Light
      </Button>
      <Button
        variant={themeName === 'oatmeal' ? 'primary' : 'ghost'}
        onPress={() => setTheme('oatmeal')}
      >
        Oatmeal
      </Button>
      <Button
        variant={themeName === 'glass' ? 'primary' : 'ghost'}
        onPress={() => setTheme('glass')}
      >
        Glass
      </Button>
    </View>
  )
}

Design Tokens

Hyena uses a token-based system for consistent styling. Import tokens directly from @hyena-studio/react-native.

Colors

import { colors } from '@hyena-studio/react-native'

// Background hierarchy (5 levels)
colors.bg.base      // '#050505' — App background
colors.bg.raised    // '#0a0a0a' — Slightly elevated
colors.bg.surface   // '#141414' — Cards, inputs
colors.bg.elevated  // '#1f1f1f' — Modals, popovers
colors.bg.overlay   // 'rgba(0,0,0,0.8)' — Backdrop

// Text hierarchy
colors.text.primary   // '#ffffff' — Main text
colors.text.secondary // 'rgba(255,255,255,0.6)' — Secondary
colors.text.muted     // 'rgba(255,255,255,0.4)' — Hints
colors.text.inverse   // '#0a0a0a' — On light backgrounds

// Borders
colors.border.default // 'rgba(255,255,255,0.1)'
colors.border.muted   // 'rgba(255,255,255,0.06)'
colors.border.strong  // 'rgba(255,255,255,0.2)'

// Accent colors (each with light/DEFAULT/dark)
colors.accent.blue.light   // '#60a5fa'
colors.accent.blue.DEFAULT // '#3b82f6'
colors.accent.blue.dark    // '#2563eb'

colors.accent.green.DEFAULT  // '#22c55e'
colors.accent.amber.DEFAULT  // '#f59e0b'
colors.accent.red.DEFAULT    // '#ef4444'
colors.accent.purple.DEFAULT // '#a855f7'

// Semantic colors
colors.semantic.success // '#22c55e'
colors.semantic.warning // '#f59e0b'
colors.semantic.error   // '#ef4444'
colors.semantic.info    // '#3b82f6'

Spacing

Based on a 4px grid system:

import { spacing, space } from '@hyena-studio/react-native'

// Numeric scale
spacing[0]  // 0
spacing[1]  // 4px
spacing[2]  // 8px
spacing[3]  // 12px
spacing[4]  // 16px
spacing[6]  // 24px
spacing[8]  // 32px
spacing[12] // 48px
spacing[16] // 64px

// Semantic aliases
space.none // 0
space.xs   // 4px
space.sm   // 8px
space.md   // 16px
space.lg   // 24px
space.xl   // 32px

Typography

import {
  fontFamilies,
  fontSizes,
  fontWeights,
  textStyles
} from '@hyena-studio/react-native'

// Font families
fontFamilies.sans // 'Inter'
fontFamilies.mono // 'JetBrains Mono'

// Font sizes
fontSizes.xs   // 12px
fontSizes.sm   // 14px
fontSizes.base // 16px
fontSizes.lg   // 18px
fontSizes.xl   // 20px
fontSizes['2xl'] // 24px
fontSizes['3xl'] // 30px
fontSizes['4xl'] // 36px

// Font weights
fontWeights.normal   // '400'
fontWeights.medium   // '500'
fontWeights.semibold // '600'
fontWeights.bold     // '700'

// Pre-composed text styles
textStyles.h1   // { fontSize: 36, fontWeight: '700', ... }
textStyles.h2   // { fontSize: 30, fontWeight: '700', ... }
textStyles.body // { fontSize: 16, fontWeight: '400', ... }
textStyles.code // { fontFamily: 'JetBrains Mono', ... }

Border Radius

import { radius } from '@hyena-studio/react-native'

radius.sm   // 6px
radius.md   // 10px
radius.lg   // 14px
radius.xl   // 20px
radius.full // 9999px (circular)

Shadows

Cross-platform shadow system:

import { shadows } from '@hyena-studio/react-native'

// Apply to any View
<View style={shadows.sm} />  // Subtle shadow
<View style={shadows.md} />  // Medium shadow
<View style={shadows.lg} />  // Large shadow
<View style={shadows.xl} />  // Extra large shadow

// Automatically uses:
// - shadowColor/shadowOffset/shadowOpacity/shadowRadius on iOS
// - elevation on Android

Using Tokens

Direct Import (Static Tokens)

Use the static token exports for theme-agnostic values:

import { colors, spacing, radius, fontSizes } from '@hyena-studio/react-native'
import { View, Text, StyleSheet } from 'react-native'

const styles = StyleSheet.create({
  card: {
    backgroundColor: colors.bg.surface,
    borderRadius: radius.lg,
    padding: spacing[4],
    borderWidth: 1,
    borderColor: colors.border.default,
  },
  title: {
    color: colors.text.primary,
    fontSize: fontSizes.lg,
    marginBottom: spacing[2],
  },
})

Theme-Aware (Dynamic Tokens)

Use useTheme() for theme-reactive styles:

import { useTheme } from '@hyena-studio/react-native'

function Card({ children }) {
  const { theme } = useTheme()

  return (
    <View style={{
      backgroundColor: theme.colors.surface,
      borderRadius: theme.radius.card || 14,
      borderColor: theme.colors.border,
    }}>
      {children}
    </View>
  )
}

With NativeWind

If using NativeWind, tokens are available as Tailwind classes:

<View className="bg-surface rounded-lg p-4 border border-default">
  <Text className="text-primary text-lg mb-2">Title</Text>
</View>

Theme Reference

Theme Colors

Each theme provides these color properties:

PropertyDescription
colors.backgroundMain app background
colors.surfaceCard/component surfaces
colors.surfaceElevatedElevated surfaces (modals, popovers)
colors.borderDefault border color
colors.borderSubtleSubtle/muted borders
colors.text.primaryMain text color
colors.text.secondarySecondary text
colors.text.mutedMuted/hint text
colors.accent.*Accent color variants
colors.semantic.*Success, warning, error, info

Dark Theme Values

{
  background: '#050505',
  surface: '#141414',
  surfaceElevated: '#1f1f1f',
  border: 'rgba(255, 255, 255, 0.1)',
  borderSubtle: 'rgba(255, 255, 255, 0.06)',
  text: {
    primary: '#ffffff',
    secondary: 'rgba(255, 255, 255, 0.6)',
    muted: 'rgba(255, 255, 255, 0.4)',
  }
}

Light Theme Values

{
  background: '#ffffff',
  surface: '#f5f5f5',
  surfaceElevated: '#ffffff',
  border: 'rgba(0, 0, 0, 0.1)',
  borderSubtle: 'rgba(0, 0, 0, 0.05)',
  text: {
    primary: '#1a1a1a',
    secondary: '#666666',
    muted: '#999999',
  }
}

Oatmeal Theme Values

{
  background: '#F5F2ED',
  surface: '#EDE9E3',
  surfaceElevated: '#FFFFFF',
  border: 'rgba(0, 0, 0, 0.08)',
  borderSubtle: 'rgba(0, 0, 0, 0.04)',
  text: {
    primary: '#2D2A26',
    secondary: '#6B6560',
    muted: '#9A938C',
  }
}

Glass Theme Values

{
  background: '#E8E4DF',
  backgroundSubtle: '#F2EFEB',
  surface: 'rgba(255, 255, 255, 0.65)',
  surfaceElevated: 'rgba(255, 255, 255, 0.8)',
  surfaceSubtle: 'rgba(255, 255, 255, 0.5)',
  border: 'rgba(255, 255, 255, 0.8)',
  borderSubtle: 'rgba(0, 0, 0, 0.06)',
  text: {
    primary: '#1a1a1a',
    secondary: '#666666',
    muted: '#888888',
    inverted: '#ffffff',
  },
  effects: {
    blur: { sm: 12, md: 24, lg: 40 },
    shadow: {
      sm: '0 2px 8px rgba(0, 0, 0, 0.04)',
      md: '0 4px 24px rgba(0, 0, 0, 0.06)',
      lg: '0 8px 40px rgba(0, 0, 0, 0.08)',
    }
  }
}

Glass Theme

The Glass theme is a complete theme (not a mode overlay) that provides glassmorphism effects with translucent surfaces and backdrop blur.

Enabling Glass Theme

import { ThemeProvider } from '@hyena-studio/react-native'

function App() {
  return (
    <ThemeProvider defaultTheme="glass">
      {/* All components render with glass styling */}
    </ThemeProvider>
  )
}

How Glass Theme Works

When the Glass theme is active:

  • Surfaces use translucent backgrounds (rgba(255,255,255,0.65))
  • Components apply backdrop blur effects via the GlassSurface wrapper
  • Borders use translucent white for frosted edge effects
  • Shadows are softer and more diffused
  • The isGlass helper from useTheme() returns true

GlassSurface Component

Build custom glass-styled components:

import { GlassSurface } from '@hyena-studio/react-native'

<GlassSurface
  intensity={16}        // Blur intensity (0-100)
  opacity={0.7}         // Background opacity
  borderRadius={14}     // Corner radius
  shadow="md"           // Shadow size: 'none' | 'sm' | 'md' | 'lg'
  bordered              // Add translucent border
>
  <Text>Content on glass surface</Text>
</GlassSurface>

Glass-Enabled Components

These components automatically apply glass styling when the Glass theme is active:

Surfaces & Containers: Card, Dialog, Sheet, Toast, Popover, EmptyState

Menus & Selection: Dropdown, ContextMenu, Select, Command, NavigationMenu, ActionSheet

Interactive Elements: AlertDialog, Tooltip, HoverCard, Accordion, Collapsible, Tabs

Layout & Navigation: Onboarding, Announcement, Sidebar, Navbar

Detecting Glass Theme

Check if Glass theme is active in your components:

import { useTheme } from '@hyena-studio/react-native'

function MyComponent() {
  const { isGlass, theme } = useTheme()

  return isGlass ? (
    <GlassSurface intensity={16} borderRadius={14}>
      <Text style={{ color: theme.colors.text.primary }}>
        Glass mode content
      </Text>
    </GlassSurface>
  ) : (
    <View style={{ backgroundColor: theme.colors.surface }}>
      <Text style={{ color: theme.colors.text.primary }}>
        Standard mode content
      </Text>
    </View>
  )
}

Recommended Backgrounds

Glass effects work best over colorful or textured backgrounds:

<View style={{
  flex: 1,
  backgroundColor: '#E8E4DF', // Warm background from glass theme
}}>
  <Card>
    {/* Card renders with glass effect */}
  </Card>
</View>

Performance Considerations

  • Backdrop blur uses backdrop-filter on web and BlurView on native
  • Glass effects can impact performance on lower-end devices
  • Consider providing a non-glass fallback for accessibility
  • Use useReducedMotion hook to respect user preferences

CSS Variables (Web)

On web, Hyena uses CSS variables for theming. This enables smooth theme transitions and runtime customization.

:root {
  --color-bg-base: #050505;
  --color-bg-surface: #141414;
  --color-text-primary: #ffffff;
  --color-accent-blue: #3b82f6;
  /* ... */
}

[data-theme="light"] {
  --color-bg-base: #ffffff;
  --color-bg-surface: #f5f5f5;
  --color-text-primary: #1a1a1a;
  /* ... */
}

Creating a Custom Theme

Step 1: Define Your Theme

// theme/custom.ts
import type { Theme } from '@hyena-studio/react-native'

export const customTheme: Theme = {
  name: 'custom',
  colors: {
    background: '#1a1625',      // Deep purple-black
    surface: '#2d2640',
    surfaceElevated: '#3d3556',
    border: 'rgba(255, 255, 255, 0.1)',
    borderSubtle: 'rgba(255, 255, 255, 0.06)',
    text: {
      primary: '#f8f4ff',
      secondary: 'rgba(248, 244, 255, 0.7)',
      muted: 'rgba(248, 244, 255, 0.4)',
    },
    accent: {
      blue: { light: '#818cf8', DEFAULT: '#6366f1', dark: '#4f46e5' },
      green: { light: '#4ade80', DEFAULT: '#22c55e', dark: '#16a34a' },
      amber: { light: '#fbbf24', DEFAULT: '#f59e0b', dark: '#d97706' },
      red: { light: '#f87171', DEFAULT: '#ef4444', dark: '#dc2626' },
      purple: { light: '#c084fc', DEFAULT: '#a855f7', dark: '#9333ea' },
    },
    semantic: {
      success: '#22c55e',
      warning: '#f59e0b',
      error: '#ef4444',
      info: '#6366f1',
    },
  },
  effects: {
    blur: 0,
    shadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
  },
  radius: {
    card: 14,
  },
}

Step 2: Register with ThemeProvider

Currently, custom themes require extending the ThemeProvider. For now, we recommend customizing the existing themes via CSS variables on web or creating a fork for native apps.