Harmony is design system focused on collaboration, reusability, and scalability.
It aims to harmonize code and Figma, provide a shared language for designers and developers, and provide consistent, reusable components for use across our platforms.
built with ❤️ from the team @Audius.
Full documentation can be found here: Harmony Docs
Install @audius/harmony:
npm install --save @audius/harmonyDue to an issue with react-virtualized, if using vite you must also install a plugin to fix the build: https://www.npmjs.com/package/esbuild-plugin-react-virtualized
npm install --save-dev esbuild-plugin-react-virtualizedFollow the instructions to add the plugin to your vite config:
// vite.config.js
import { defineConfig } from 'vite'
import fixReactVirtualized from 'esbuild-plugin-react-virtualized'
export default defineConfig({
optimizeDeps: {
esbuildOptions: {
plugins: [fixReactVirtualized]
}
}
})For more information, see: bvaughn/react-virtualized#1722
Import styles exported by Harmony
import '@audius/harmony/dist/harmony.css'Setup the ThemeProvider exported by Harmony
import { ThemeProvider as HarmonyThemeProvider } from '@audius/harmony'
const App = () => {
return <HarmonyThemeProvider theme='day'>...</HarmonyThemeProvider>
}In order use emotion yourself, follow their documentation for setting up the css-prop
If using typescript you will need to:
- Add an emotion.d.ts file and include the following for access to harmony's theme type
import '@emotion/react'
import type { HarmonyTheme } from '@audius/harmony'
declare module '@emotion/react' {
export interface Theme extends HarmonyTheme {}
}- Update your tsconfig to specify the jsxImportLocation:
{
"compilerOptions": {
"jsxImportSource": "@emotion/react",
...
}
}import { Button, Flex } from '@audius/harmony'
const App = () => {
return (
<Flex gap='m'>
<Button variant='secondary'>Click This!</Button>
<Button>Click That!</Button>
</Flex>
)
}A Contribution Guide is available here.
Harmony includes utilities to help build responsive designs consistently across the application.
The breakpoints module provides standardized screen size breakpoints and media query helpers:
import { breakpoints } from '@audius/harmony'
// Access specific breakpoint values
const tabletWidth = breakpoints.values.md // 1024
// Use predefined media queries
const mobileQuery = breakpoints.down.sm // (max-width: 768px)
const desktopQuery = breakpoints.up.md // (min-width: 1025px)
const tabletQuery = breakpoints.between.sm_md // (min-width: 769px) and (max-width: 1024px)
// Create custom media queries
const customQuery = breakpoints.createCustomQuery(500, 800) // (min-width: 500px) and (max-width: 800px)For reactive responsive designs, use the useMedia hook:
import { useMedia } from '@audius/harmony'
const MyComponent = () => {
const {
// Common device categories
isMobile, // <= 768px
isTablet, // > 768px and <= 1024px
isDesktop, // > 1024px
// Detailed breakpoint checks
isExtraSmall, // <= 480px
isSmall, // <= 768px
isMedium, // <= 1024px
// Check custom queries
matchesQuery
} = useMedia()
return (
<div>
{isMobile && <MobileLayout />}
{isTablet && <TabletLayout />}
{isDesktop && <DesktopLayout />}
{/* Check a custom query */}
{matchesQuery('(orientation: portrait)') && <PortraitContent />}
</div>
)
}For more maintainable responsive styling with Emotion, use the createResponsiveStyles utility:
import { useMedia, createResponsiveStyles } from '@audius/harmony'
const MyComponent = () => {
const media = useMedia()
const { spacing } = useTheme()
// Define styles for different breakpoints
const styles = createResponsiveStyles(media, {
// For a single element
container: {
base: {
padding: spacing.l,
display: 'flex'
},
mobile: {
padding: spacing.m,
flexDirection: 'column'
},
tablet: {
padding: spacing.l,
flexDirection: 'row',
flexWrap: 'wrap'
}
},
// Include multiple elements in one call
header: {
base: { fontSize: '24px' },
mobile: { fontSize: '18px' }
},
// Use functions for complex conditional logic
content: {
base: { marginTop: spacing.m },
mobile: (currentMedia) => ({
// isExtraSmall is for phones (≤ 480px)
...(currentMedia.isExtraSmall && {
marginTop: spacing.s,
fontSize: '14px'
})
})
}
})
return (
<div css={styles.container}>
<h1 css={styles.header}>Title</h1>
<div css={styles.content}>Content</div>
</div>
)
}The utility applies styles in this order, with later ones overriding earlier ones:
- Base styles (always applied)
- Mobile styles (if screen width ≤ 768px)
- Tablet styles (if 768px < width ≤ 1024px)
- Desktop styles (if width > 1024px)
This approach improves maintainability by:
- Grouping related styles by component part
- Keeping responsive logic out of JSX
- Making breakpoint-specific styles easy to locate and update
For complex components with many responsive styles, it's beneficial to extract the styles into a separate file:
// Button.styles.ts
import { createResponsiveStyles, useMedia } from '@audius/harmony'
type MediaContext = ReturnType<typeof useMedia>
export const getButtonStyles = (
media: MediaContext,
spacing: Record<string, string | number>
) => {
return createResponsiveStyles(media, {
container: {
base: { display: 'flex', padding: spacing.m },
mobile: { flexDirection: 'column' }
}
// More styles...
})
}
// Button.tsx
import { getButtonStyles } from './Button.styles'
export const Button = () => {
const media = useMedia()
const { spacing } = useTheme()
// Import styles from separate file
const styles = getButtonStyles(media, spacing)
return <div css={styles.container}>{/* Component content */}</div>
}This pattern:
- Keeps component logic and style definitions separate
- Makes the component file more readable
- Promotes reusability of styles
- Makes it easier to maintain complex responsive UIs