The @proton/atoms package contains the foundational UI components built on atomic design principles. These are the lowest-level building blocks used throughout Proton applications.
Installation
{
"name" : "@proton/atoms" ,
"license" : "GPL-3.0" ,
"sideEffects" : false ,
"type" : "module" ,
"main" : "./src/index.ts"
}
Module Type : This package uses ES modules with explicit export paths defined in package.json.
Design Philosophy
Atomic Design
Accessibility First
Polymorphic
Components are minimal, focused, and composable
Single responsibility
Minimal dependencies
Highly reusable
Type-safe APIs
Built with accessibility as a priority
ARIA labels and roles
Keyboard navigation
Screen reader support
Focus management
Components can render as different HTML elements
Type-safe polymorphism
Flexible composition
Semantic HTML support
Component Catalog
Button Primary button component with polymorphic rendering
ButtonLike Make any element look and behave like a button
InlineLinkButton Inline button styled as a link
import { Button } from '@proton/atoms/Button/Button' ;
import { ButtonLike } from '@proton/atoms/Button/ButtonLike' ;
// Standard button
< Button color = "norm" shape = "solid" size = "medium" >
Click me
</ Button >
// Button with different shapes
< Button shape = "outline" > Outline </ Button >
< Button shape = "ghost" > Ghost </ Button >
< Button shape = "solid" > Solid </ Button >
// Button sizes
< Button size = "small" > Small </ Button >
< Button size = "medium" > Medium </ Button >
< Button size = "large" > Large </ Button >
// Polymorphic - render as a link
< ButtonLike as = "a" href = "/page" shape = "solid" >
Link Button
</ ButtonLike >
import { Input } from '@proton/atoms/Input/Input' ;
< Input
type = "text"
placeholder = "Enter text"
value = { value }
onChange = { ( e ) => setValue ( e . target . value ) }
error = { error }
disabled = { disabled }
/>
// With icon
< Input
prefix = { < Icon name = "magnifier" /> }
placeholder = "Search..."
/>
Layout Components
import { Card } from '@proton/atoms/Card/Card' ;
< Card rounded bordered background = "weak" >
< h3 > Card Title </ h3 >
< p > Card content goes here </ p >
</ Card >
import { Panel } from '@proton/atoms/Panel/Panel' ;
import { PanelHeader } from '@proton/atoms/Panel/PanelHeader' ;
< Panel >
< PanelHeader > Panel Title </ PanelHeader >
< div className = "p-4" >
Panel content
</ div >
</ Panel >
import { DualPaneSidebar } from '@proton/atoms/DualPane/DualPaneSidebar' ;
import { DualPaneContent } from '@proton/atoms/DualPane/DualPaneContent' ;
< div className = "flex" >
< DualPaneSidebar >
Sidebar navigation
</ DualPaneSidebar >
< DualPaneContent >
Main content
</ DualPaneContent >
</ div >
Data Visualization
import { Donut } from '@proton/atoms/Donut/Donut' ;
< Donut
segments = { [
{ value: 30 , color: 'var(--primary)' },
{ value: 50 , color: 'var(--signal-success)' },
{ value: 20 , color: 'var(--signal-warning)' },
] }
/>
import { CircleLoader } from '@proton/atoms/CircleLoader/CircleLoader' ;
import { ProtonLoader } from '@proton/atoms/ProtonLoader/ProtonLoader' ;
// Circle loader for progress
< CircleLoader size = "large" />
// Branded Proton loader
< ProtonLoader />
Navigation & Steps
import { Stepper } from '@proton/atoms/Stepper/Stepper' ;
import { Step } from '@proton/atoms/Stepper/Step' ;
import { StepIndicator } from '@proton/atoms/Stepper/StepIndicator' ;
< Stepper activeStep = { 1 } >
< Step >
< StepIndicator > 1 </ StepIndicator >
Account setup
</ Step >
< Step >
< StepIndicator > 2 </ StepIndicator >
Verification
</ Step >
< Step >
< StepIndicator > 3 </ StepIndicator >
Complete
</ Step >
</ Stepper >
Vertical Steps
import { VerticalSteps } from '@proton/atoms/VerticalSteps/VerticalSteps' ;
import { VerticalStep } from '@proton/atoms/VerticalSteps/VerticalStep' ;
< VerticalSteps >
< VerticalStep status = "complete" > Step 1 complete </ VerticalStep >
< VerticalStep status = "current" > Step 2 in progress </ VerticalStep >
< VerticalStep status = "upcoming" > Step 3 upcoming </ VerticalStep >
</ VerticalSteps >
Badges & Indicators
Pill Small status indicators
NotificationDot Notification indicator dot
NotificationCounter Numeric notification badge
CircledNumber Number in a circle
import { Pill } from '@proton/atoms/Pill/Pill' ;
import { NotificationDot } from '@proton/atoms/NotificationDot/NotificationDot' ;
import { NotificationCounter } from '@proton/atoms/NotificationCounter/NotificationCounter' ;
< Pill color = "success" > Active </ Pill >
< Pill color = "danger" > Error </ Pill >
< Pill color = "warning" > Warning </ Pill >
< NotificationDot color = "danger" />
< NotificationCounter count = { 5 } />
Feedback Components
import { Banner } from '@proton/atoms/Banner/Banner' ;
import { Tooltip } from '@proton/atoms/Tooltip/Tooltip' ;
// Banners for important messages
< Banner color = "danger" icon = "triangle-exclamation" >
Important warning message
</ Banner >
< Banner color = "info" icon = "info-circle" >
Helpful information
</ Banner >
// Tooltips
< Tooltip title = "Helpful explanation" >
< button > Hover me </ button >
</ Tooltip >
Interactive Controls
import { Slider } from '@proton/atoms/Slider/Slider' ;
import { SliderMark } from '@proton/atoms/Slider/SliderMark' ;
< Slider
min = { 0 }
max = { 100 }
value = { value }
onChange = { setValue }
marks = {
<>
< SliderMark value = { 0 } > 0 </ SliderMark >
< SliderMark value = { 50 } > 50 </ SliderMark >
< SliderMark value = { 100 } > 100 </ SliderMark >
</>
}
/>
import { Scroll } from '@proton/atoms/Scroll/Scroll' ;
import { ScrollShadows } from '@proton/atoms/Scroll/ScrollShadows' ;
// Custom scrollable area
< Scroll >
< div > Long content... </ div >
</ Scroll >
// With shadows indicating scroll
< ScrollShadows >
< div > Scrollable content </ div >
</ ScrollShadows >
User Interface
import { Avatar } from '@proton/atoms/Avatar/Avatar' ;
import { UserAvatar } from '@proton/atoms/UserAvatar/UserAvatar' ;
import { getAccentColorForUsername } from '@proton/atoms/UserAvatar/getAccentColorForUsername' ;
import { Kbd } from '@proton/atoms/Kbd/Kbd' ;
import { Vr } from '@proton/atoms/Vr/Vr' ;
// Avatars
< Avatar > JD </ Avatar >
< UserAvatar name = "John Doe" email = "[email protected] " />
// Keyboard shortcuts
< span > Press < Kbd > Ctrl </ Kbd > + < Kbd > S </ Kbd > to save </ span >
// Vertical divider
< Vr />
Dashboard Components
import { DashboardGrid } from '@proton/atoms/DashboardGrid/DashboardGrid' ;
import { DashboardCard } from '@proton/atoms/DashboardCard/DashboardCard' ;
< DashboardGrid >
< DashboardCard >
< h3 > Storage </ h3 >
< p > 50 GB used </ p >
</ DashboardCard >
< DashboardCard >
< h3 > Messages </ h3 >
< p > 1,234 emails </ p >
</ DashboardCard >
</ DashboardGrid >
Links & Navigation
import { Href } from '@proton/atoms/Href/Href' ;
// Type-safe external links
< Href href = "https://proton.me" > Visit Proton </ Href >
// Opens in new tab with security
< Href href = "https://example.com" target = "_blank" >
External link
</ Href >
Import Pattern
Explicit Imports : Use the full import path for optimal tree-shaking:import { Button } from '@proton/atoms/Button/Button' ;
import { Tooltip } from '@proton/atoms/Tooltip/Tooltip' ;
Package.json exports define exact paths:
{
"exports" : {
"./Button/Button" : "./src/Button/Button.tsx" ,
"./Tooltip/Tooltip" : "./src/Tooltip/Tooltip.tsx"
}
}
Styling
Components use CSS classes from @proton/styles:
// Components have design tokens
< Button color = "norm" > // Uses --interaction-norm color
< Button color = "danger" > // Uses --signal-danger color
// Custom styling via className
< Button className = "my-custom-class" >
Custom styled button
</ Button >
Creating Atoms
The package includes a generator for new atoms:
yarn workspace @proton/atoms create-atom
This creates the component structure:
MyAtom/
├── MyAtom.tsx
├── MyAtom.scss
└── MyAtom.test.tsx
Testing
# Run tests
yarn workspace @proton/atoms test
# Watch mode
yarn workspace @proton/atoms test:watch
# Coverage
yarn workspace @proton/atoms test:ci
Testing Example
import { render , screen } from '@testing-library/react' ;
import { Button } from '@proton/atoms/Button/Button' ;
test ( 'renders button' , () => {
render ( < Button > Click me </ Button > );
expect ( screen . getByRole ( 'button' )). toHaveTextContent ( 'Click me' );
});
Dependencies
{
"@proton/colors" : "workspace:^" ,
"@proton/hooks" : "workspace:^" ,
"@proton/icons" : "workspace:^" ,
"@proton/styles" : "workspace:^" ,
"@proton/utils" : "workspace:^"
}
Best Practices
Composition over Customization : Compose atoms to build complex components rather than heavily customizing individual atoms.
Polymorphic Props : Use the as prop to render components with different HTML elements:< ButtonLike as = "a" href = "/link" > Link as button </ ButtonLike >
< ButtonLike as = "div" onClick = { handler } > Div as button </ ButtonLike >
Accessibility : All atoms include proper ARIA attributes. Ensure you maintain accessibility when composing them.
TypeScript Support
Full TypeScript support with polymorphic types:
import type { ButtonProps } from '@proton/atoms/Button/Button' ;
import type { PolymorphicComponentProps } from '@proton/react-polymorphic-types' ;
// Polymorphic button that can be rendered as any element
type MyButtonProps < E extends React . ElementType > =
PolymorphicComponentProps < E , ButtonProps >;
Design Tokens
Components use CSS custom properties from the design system:
// Color tokens
--interaction-norm
--interaction-norm-hover
--interaction-norm-active
--signal-danger
--signal-warning
--signal-success
--signal-info
// Spacing tokens
--space-1
--space-2
--space-4
// Border radius
--border-radius-sm
--border-radius-md
--border-radius-lg