Atoms
What are Atoms?
Section titled “What are Atoms?”Atoms are the basic building blocks of matter applied to web interfaces. They’re the foundational HTML elements that can’t be broken down any further without losing their meaning.
Characteristics
Section titled “Characteristics”- Indivisible - Can’t be meaningfully broken down further
- Single Purpose - Do one thing well
- Highly Reusable - Used across many molecules and organisms
- Abstract - Function independently of context
- Styled - Have consistent styling defined
Common Atoms
Section titled “Common Atoms”Buttons
Section titled “Buttons”---interface Props { variant?: 'primary' | 'secondary' | 'danger' | 'ghost'; size?: 'sm' | 'md' | 'lg'; type?: 'button' | 'submit' | 'reset'; disabled?: boolean;}
const { variant = 'primary', size = 'md', type = 'button', disabled = false} = Astro.props;---
<button type={type} disabled={disabled} class:list={['btn', `btn-${variant}`, `btn-${size}`]}> <slot /></button>
<style> .btn { display: inline-flex; align-items: center; justify-content: center; border: none; border-radius: 0.375rem; font-weight: 500; cursor: pointer; transition: all 0.2s; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Sizes */ .btn-sm { padding: 0.25rem 0.75rem; font-size: 0.875rem; }
.btn-md { padding: 0.5rem 1rem; font-size: 1rem; }
.btn-lg { padding: 0.75rem 1.5rem; font-size: 1.125rem; }
/* Variants */ .btn-primary { background: var(--color-primary); color: white; }
.btn-primary:hover:not(:disabled) { background: var(--color-primary-dark); }
.btn-secondary { background: var(--color-secondary); color: white; }
.btn-danger { background: var(--color-danger); color: white; }
.btn-ghost { background: transparent; color: var(--color-text); border: 1px solid var(--color-border); }
.btn-ghost:hover:not(:disabled) { background: var(--color-background-hover); }</style>Input Fields
Section titled “Input Fields”---interface Props { type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url'; name: string; placeholder?: string; value?: string; required?: boolean; disabled?: boolean; error?: boolean;}
const { type = 'text', name, placeholder, value, required = false, disabled = false, error = false} = Astro.props;---
<input type={type} name={name} placeholder={placeholder} value={value} required={required} disabled={disabled} class:list={['input', { 'input-error': error }]}/>
<style> .input { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border); border-radius: 0.375rem; font-size: 1rem; transition: all 0.2s; }
.input:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-primary-alpha); }
.input:disabled { background: var(--color-background-disabled); cursor: not-allowed; }
.input-error { border-color: var(--color-danger); }
.input-error:focus { border-color: var(--color-danger); box-shadow: 0 0 0 3px var(--color-danger-alpha); }</style>Labels
Section titled “Labels”---interface Props { for?: string; required?: boolean;}
const { for: htmlFor, required = false } = Astro.props;---
<label for={htmlFor} class="label"> <slot /> {required && <span class="required">*</span>}</label>
<style> .label { display: block; font-size: 0.875rem; font-weight: 500; color: var(--color-text); margin-bottom: 0.25rem; }
.required { color: var(--color-danger); margin-left: 0.25rem; }</style>---interface Props { name: string; size?: 'sm' | 'md' | 'lg'; color?: string;}
const { name, size = 'md', color } = Astro.props;
const sizeMap = { sm: 16, md: 20, lg: 24};
const iconSize = sizeMap[size];---
<svg width={iconSize} height={iconSize} class="icon" style={color ? `color: ${color}` : undefined} aria-hidden="true"> <use href={`/icons/sprite.svg#${name}`}></use></svg>
<style> .icon { display: inline-block; vertical-align: middle; fill: currentColor; }</style>Typography
Section titled “Typography”---interface Props { level: 1 | 2 | 3 | 4 | 5 | 6; as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';}
const { level, as } = Astro.props;const Tag = as || `h${level}`;---
<Tag class={`heading heading-${level}`}> <slot /></Tag>
<style> .heading { font-weight: 600; line-height: 1.2; color: var(--color-heading); }
.heading-1 { font-size: 2.5rem; } .heading-2 { font-size: 2rem; } .heading-3 { font-size: 1.75rem; } .heading-4 { font-size: 1.5rem; } .heading-5 { font-size: 1.25rem; } .heading-6 { font-size: 1rem; }</style>Badges
Section titled “Badges”---interface Props { variant?: 'default' | 'success' | 'warning' | 'danger' | 'info'; size?: 'sm' | 'md';}
const { variant = 'default', size = 'md' } = Astro.props;---
<span class:list={['badge', `badge-${variant}`, `badge-${size}`]}> <slot /></span>
<style> .badge { display: inline-flex; align-items: center; font-weight: 500; border-radius: 9999px; }
.badge-sm { padding: 0.125rem 0.5rem; font-size: 0.75rem; }
.badge-md { padding: 0.25rem 0.75rem; font-size: 0.875rem; }
.badge-default { background: var(--color-background-secondary); color: var(--color-text); }
.badge-success { background: var(--color-success-light); color: var(--color-success-dark); }
.badge-warning { background: var(--color-warning-light); color: var(--color-warning-dark); }
.badge-danger { background: var(--color-danger-light); color: var(--color-danger-dark); }
.badge-info { background: var(--color-info-light); color: var(--color-info-dark); }</style>---interface Props { href: string; variant?: 'default' | 'primary' | 'muted'; external?: boolean;}
const { href, variant = 'default', external = false } = Astro.props;
const externalProps = external ? { target: '_blank', rel: 'noopener noreferrer' } : {};---
<a href={href} class:list={['link', `link-${variant}`]} {...externalProps}> <slot /></a>
<style> .link { text-decoration: none; transition: color 0.2s; }
.link-default { color: var(--color-text); text-decoration: underline; }
.link-default:hover { color: var(--color-primary); }
.link-primary { color: var(--color-primary); }
.link-primary:hover { color: var(--color-primary-dark); }
.link-muted { color: var(--color-text-muted); }
.link-muted:hover { color: var(--color-text); }</style>Best Practices for Atoms
Section titled “Best Practices for Atoms”✅ Keep atoms simple and focused ✅ Make atoms highly reusable ✅ Use consistent naming conventions ✅ Provide clear prop interfaces ✅ Include all necessary variants ✅ Add proper accessibility attributes ✅ Document usage examples ✅ Test atoms in isolation
Don’ts
Section titled “Don’ts”❌ Add business logic to atoms ❌ Make atoms depend on context ❌ Create overly specific atoms ❌ Skip accessibility considerations ❌ Hardcode values that should be props ❌ Create atoms that are too complex ❌ Forget to handle edge cases
Testing Atoms
Section titled “Testing Atoms”// Example with Testing Libraryimport { render, screen } from '@testing-library/svelte';import Button from './Button.svelte';
describe('Button Atom', () => { test('renders with text', () => { render(Button, { props: { children: 'Click me' } }); expect(screen.getByRole('button')).toHaveTextContent('Click me'); });
test('applies variant classes', () => { render(Button, { props: { variant: 'primary' } }); expect(screen.getByRole('button')).toHaveClass('btn-primary'); });
test('disables button when disabled prop is true', () => { render(Button, { props: { disabled: true } }); expect(screen.getByRole('button')).toBeDisabled(); });});Documentation
Section titled “Documentation”Use tools like Storybook to document atoms:
import type { Meta, StoryObj } from '@storybook/react';import Button from './Button';
const meta: Meta<typeof Button> = { title: 'Atoms/Button', component: Button, tags: ['autodocs'],};
export default meta;type Story = StoryObj<typeof Button>;
export const Primary: Story = { args: { variant: 'primary', children: 'Primary Button', },};
export const Secondary: Story = { args: { variant: 'secondary', children: 'Secondary Button', },};