Skip to main content
The Medusa admin dashboard is highly customizable, allowing you to tailor the appearance and branding to match your business identity.

Theming

The admin dashboard supports light and dark themes out of the box, with automatic system preference detection.

Theme Provider

The dashboard uses a ThemeProvider (packages/admin/dashboard/src/providers/theme-provider/theme-provider.tsx) that:
  • Persists theme selection to localStorage
  • Supports light, dark, and system options
  • Applies theme changes without page reload
  • Manages smooth transitions between themes

Theme Context

Access the current theme in your custom components:
import { useTheme } from "@medusajs/dashboard"

export default function CustomWidget() {
  const { theme, setTheme } = useTheme()
  
  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme("dark")}>Dark Mode</button>
      <button onClick={() => setTheme("light")}>Light Mode</button>
      <button onClick={() => setTheme("system")}>System</button>
    </div>
  )
}

Color Customization

The dashboard uses CSS variables for theming. You can override these in your own CSS:
/* Custom theme colors */
:root {
  --medusa-bg-base: #ffffff;
  --medusa-fg-base: #1a1a1a;
  --medusa-border-base: #e0e0e0;
  
  /* Primary brand color */
  --medusa-bg-interactive: #6366f1;
  --medusa-fg-interactive: #ffffff;
}

.dark {
  --medusa-bg-base: #1a1a1a;
  --medusa-fg-base: #ffffff;
  --medusa-border-base: #2a2a2a;
}

Branding

To replace the Medusa logo with your own:
1

Prepare Your Logo

Create SVG or PNG versions of your logo:
  • Full logo (for sidebar)
  • Icon/mark (for collapsed sidebar)
  • Recommended size: SVG or 200x50px PNG
2

Create a Custom Component

Create a custom route or widget to override the logo:
// src/admin/widgets/custom-logo.tsx
import { defineWidgetConfig } from "@medusajs/admin-sdk"

const CustomLogo = () => {
  return (
    <img 
      src="/path/to/your-logo.svg" 
      alt="Your Brand"
      className="h-8"
    />
  )
}

export const config = defineWidgetConfig({
  zone: "login.before"
})

export default CustomLogo
3

Add Custom Styles

Override default branding with CSS:
/* Hide default Medusa branding */
[data-medusa-logo] {
  display: none;
}

/* Style your logo */
.custom-logo {
  max-height: 32px;
  width: auto;
}

Custom Fonts

Add custom fonts to match your brand:
/* Import custom font */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

:root {
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

body {
  font-family: var(--font-sans);
}

Favicon

To customize the favicon:
  1. Add your favicon files to the public directory
  2. Update the HTML head in your build configuration
<link rel="icon" type="image/svg+xml" href="/your-favicon.svg" />
<link rel="icon" type="image/png" href="/your-favicon.png" />

Layout Customization

The sidebar can be customized through the SidebarProvider:
import { useSidebar } from "@medusajs/dashboard"

export default function CustomWidget() {
  const { isOpen, toggle, desktop } = useSidebar()
  
  return (
    <button onClick={toggle}>
      {isOpen ? "Collapse" : "Expand"} Sidebar
    </button>
  )
}

Custom Navigation

Add custom menu items to the sidebar:
// src/admin/routes/custom-page/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { BuildingStorefront } from "@medusajs/icons"

const CustomPage = () => {
  return (
    <div>
      <h1>Custom Page</h1>
    </div>
  )
}

export const config = defineRouteConfig({
  label: "Custom Page",
  icon: BuildingStorefront,
  rank: 100 // Controls ordering in sidebar
})

export default CustomPage

Styling Components

Using Medusa UI Components

The dashboard includes the @medusajs/ui design system:
import { Container, Heading, Text, Button } from "@medusajs/ui"

export default function CustomWidget() {
  return (
    <Container>
      <Heading level="h2">Welcome</Heading>
      <Text size="small" className="text-ui-fg-subtle">
        This is a custom widget
      </Text>
      <Button variant="primary">Take Action</Button>
    </Container>
  )
}

Tailwind CSS

The dashboard uses Tailwind CSS for styling. All Tailwind utilities are available:
export default function CustomComponent() {
  return (
    <div className="flex items-center gap-4 p-4 rounded-lg border border-ui-border-base bg-ui-bg-subtle">
      <div className="flex-1">
        <h3 className="text-lg font-semibold text-ui-fg-base">
          Custom Card
        </h3>
        <p className="text-sm text-ui-fg-subtle">
          Styled with Tailwind utilities
        </p>
      </div>
    </div>
  )
}

Custom CSS

Add global styles by creating a CSS file:
/* src/admin/styles/custom.css */

/* Override default spacing */
.custom-container {
  @apply px-8 py-6;
}

/* Custom card style */
.custom-card {
  @apply rounded-xl shadow-lg border-2 border-ui-border-strong;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

/* Custom button variant */
.btn-custom {
  @apply bg-gradient-to-r from-purple-600 to-indigo-600;
  @apply text-white font-semibold px-6 py-3 rounded-lg;
  @apply hover:from-purple-700 hover:to-indigo-700;
  @apply transition-all duration-200;
}
Import the CSS in your widget or route:
import "./styles/custom.css"

export default function CustomWidget() {
  return (
    <div className="custom-card">
      <button className="btn-custom">Custom Button</button>
    </div>
  )
}

Advanced Customization

Custom Provider

Wrap the dashboard in your own provider:
import { DashboardApp } from "@medusajs/dashboard"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30000,
      retry: 2
    }
  }
})

const app = new DashboardApp({ plugins })

// Wrap with custom provider
function CustomDashboard() {
  return (
    <QueryClientProvider client={queryClient}>
      {app.render()}
    </QueryClientProvider>
  )
}

Internationalization

Customize or add translations:
// src/admin/i18n/custom-translations.ts
export const customTranslations = {
  en: {
    custom: {
      welcome: "Welcome to {{storeName}}",
      dashboard: "Dashboard Overview"
    }
  },
  es: {
    custom: {
      welcome: "Bienvenido a {{storeName}}",
      dashboard: "Vista General del Panel"
    }
  }
}
Use translations in your components:
import { useTranslation } from "react-i18next"

export default function CustomWidget() {
  const { t } = useTranslation("custom")
  
  return (
    <div>
      <h1>{t("welcome", { storeName: "My Store" })}</h1>
      <p>{t("dashboard")}</p>
    </div>
  )
}

Best Practices

Always use CSS variables and Tailwind utilities instead of hard-coded colors to ensure your customizations work in both light and dark modes.
Leverage the @medusajs/ui components for consistency and automatic theme support.
Ensure your customizations work well on mobile, tablet, and desktop screens.
Keep custom CSS minimal and avoid heavy JavaScript in the main bundle.

Next Steps

Build docs developers (and LLMs) love