| name | atomic-design-molecules |
| description | Use when composing atoms into molecule components like form fields, search bars, and card headers. Molecules are functional groups of atoms. |
| allowed-tools | ["Bash","Read","Write","Edit","Glob","Grep"] |
Atomic Design: Molecules
Master the creation of molecule components - functional groups of atoms that work together as a unit. Molecules combine multiple atoms to create more complex, purposeful UI elements.
What Are Molecules?
Molecules are the first level of composition in Atomic Design. They are:
- Composed of atoms only: Never include other molecules
- Single purpose: Do one thing well
- Functional units: Atoms working together for a specific task
- Reusable: Used across different organisms and contexts
- Minimally stateful: May have limited internal state for UI concerns
Common Molecule Types
Form Molecules
- Form fields (label + input + error)
- Search forms (input + button)
- Toggle groups (label + toggle)
- Date pickers (input + calendar trigger)
- File uploaders (dropzone + button)
Navigation Molecules
- Nav items (icon + text + indicator)
- Breadcrumb items (link + separator)
- Pagination controls (buttons + page indicator)
- Tab items (icon + label)
Display Molecules
- Media objects (avatar + text)
- Card headers (title + subtitle + action)
- List items (checkbox + content + actions)
- Stat displays (label + value + trend)
Action Molecules
- Button groups (multiple buttons)
- Dropdown triggers (button + icon)
- Icon buttons (icon + tooltip)
- Action menus (button + menu items)
FormField Molecule Example
Complete Implementation
import React from 'react';
import { Label } from '@/components/atoms/Label';
import { Input, type InputProps } from '@/components/atoms/Input';
import { Text } from '@/components/atoms/Typography';
import styles from './FormField.module.css';
export interface FormFieldProps extends InputProps {
label: string;
name: string;
helpText?: string;
error?: string;
required?: boolean;
}
export const FormField = React.forwardRef<HTMLInputElement, FormFieldProps>(
(
{
label,
name,
helpText,
error,
required = ,
id,
className,
...inputProps
},
ref
) => {
fieldId = id || ;
helpTextId = helpText ? : ;
errorId = error ? : ;
describedBy = [helpTextId, errorId].().() || ;
(
);
}
);
. = ;
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.helpText {
margin-top: 2px;
}
.error {
margin-top: 2px;
display: flex;
align-items: center;
gap: 4px;
}
SearchForm Molecule Example
import React, { useState, useCallback } from 'react';
import { Input } from '@/components/atoms/Input';
import { Button } from '@/components/atoms/Button';
import { Icon } from '@/components/atoms/Icon';
import styles from './SearchForm.module.css';
export interface SearchFormProps {
placeholder?: string;
defaultValue?: string;
onSubmit: (query: string) => void;
onChange?: (query: string) => void;
isLoading?: boolean;
size?: 'sm' | 'md' | 'lg';
?: ;
}
: .<> = {
[query, setQuery] = (defaultValue);
handleChange = (
{
value = e..;
(value);
onChange?.(value);
},
[onChange]
);
handleSubmit = (
{
e.();
(query.());
},
[onSubmit, query]
);
handleClear = ( {
();
onChange?.();
}, [onChange]);
(
);
};
. = ;
.form {
display: flex;
gap: 8px;
align-items: stretch;
}
.clearButton {
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
cursor: pointer;
padding: 4px;
color: var(--color-neutral-500);
transition: color 150ms;
}
.clearButton:hover {
color: var(--color-neutral-700);
}
MediaObject Molecule Example
import React from 'react';
import { Avatar, type AvatarProps } from '@/components/atoms/Avatar';
import { Text, Heading } from '@/components/atoms/Typography';
import styles from './MediaObject.module.css';
export interface MediaObjectProps {
avatarSrc?: string;
avatarAlt: string;
avatarInitials?: string;
avatarSize?: AvatarProps['size'];
title: React.ReactNode;
subtitle?: React.ReactNode;
meta?: React.ReactNode;
action?: React.;
?: | | ;
?: ;
}
: .<> = {
classNames = [styles., styles[], className]
.()
.();
(
);
};
. = ;
NavItem Molecule Example
import React from 'react';
import { Icon } from '@/components/atoms/Icon';
import { Badge } from '@/components/atoms/Badge';
import styles from './NavItem.module.css';
export interface NavItemProps {
icon?: string;
label: string;
href: string;
isActive?: boolean;
badge?: number;
disabled?: boolean;
onClick?: (e: React.MouseEvent) => void;
}
export const NavItem: React.FC<NavItemProps> = ({
icon,
label,
href,
isActive = ,
badge,
disabled = ,
onClick,
}) => {
classNames = [
styles.,
isActive && styles.,
disabled && styles.,
]
.()
.();
= () => {
(disabled) {
e.();
;
}
onClick?.(e);
};
(
);
};
. = ;
CardHeader Molecule Example
import React from 'react';
import { Heading, Text } from '@/components/atoms/Typography';
import { Icon } from '@/components/atoms/Icon';
import { Button } from '@/components/atoms/Button';
import styles from './CardHeader.module.css';
export interface CardHeaderProps {
title: string;
subtitle?: string;
icon?: string;
actionLabel?: string;
onAction?: () => void;
className?: string;
}
export const CardHeader: React.FC<CardHeaderProps> = ({
title,
subtitle,
icon,
actionLabel,
onAction,
className,
}) => {
(
);
};
. = ;
ListItem Molecule Example
import React from 'react';
import { Checkbox } from '@/components/atoms/Checkbox';
import { Text } from '@/components/atoms/Typography';
import { Icon } from '@/components/atoms/Icon';
import styles from './ListItem.module.css';
export interface ListItemProps {
id: string;
primary: React.ReactNode;
secondary?: React.ReactNode;
icon?: string;
selectable?: boolean;
selected?: boolean;
onSelect?: (id: string, selected: boolean) => void;
?: .;
?: ;
}
: .<> = {
classNames = [
styles.,
onClick && styles.,
selected && styles.,
]
.()
.();
= () => {
onSelect?.(id, e..);
};
(
);
};
. = ;
ButtonGroup Molecule Example
import React from 'react';
import { Button, type ButtonProps } from '@/components/atoms/Button';
import styles from './ButtonGroup.module.css';
export interface ButtonGroupItem {
id: string;
label: string;
icon?: string;
disabled?: boolean;
}
export interface ButtonGroupProps {
items: ButtonGroupItem[];
value?: string | string[];
onChange?: (value: string | string[]) => void;
multiple?: boolean;
size?: ButtonProps['size'];
disabled?: ;
}
: .<> = {
selectedIds = .(value) ? value : [value].();
= () => {
(!onChange) ;
(multiple) {
newValue = selectedIds.(itemId)
? selectedIds.( id !== itemId)
: [...selectedIds, itemId];
(newValue);
} {
(itemId);
}
};
(
);
};
. = ;
Stat Molecule Example
import React from 'react';
import { Text, Heading } from '@/components/atoms/Typography';
import { Icon } from '@/components/atoms/Icon';
import styles from './Stat.module.css';
export type TrendDirection = 'up' | 'down' | 'neutral';
export interface StatProps {
label: string;
value: string | number;
previousValue?: string | number;
trend?: TrendDirection;
trendValue?: string;
icon?: string;
helpText?: string;
}
export const Stat: .<> = {
= () => {
(direction) {
:
;
:
;
:
;
}
};
= () => {
(direction) {
:
;
:
;
:
;
}
};
(
);
};
. = ;
Best Practices
1. Keep Molecules Focused
const SearchForm = () => (
<form>
<Input placeholder="Search..." />
<Button>Search</Button>
</form>
);
const SearchWithFiltersAndResults = () => (
<div>
<Input />
<Button>Search</Button>
<FilterDropdown /> {/* Should be separate molecule */}
<ResultsList /> {/* Should be organism */}
<Pagination /> {/* Should be separate molecule */}
</div>
);
2. Only Import from Atoms
import { Button } from '@/components/atoms/Button';
import { Input } from '@/components/atoms/Input';
import { Icon } from '@/components/atoms/Icon';
import { FormField } from '@/components/molecules/FormField';
import { Button } from '@/components/atoms/Button';
3. Compose Props Carefully
interface SearchFormProps {
onSubmit: (query: string) => void;
inputProps?: Partial<InputProps>;
buttonProps?: Partial<ButtonProps>;
}
interface SearchFormProps {
inputPlaceholder?: string;
inputDisabled?: boolean;
inputSize?: string;
buttonVariant?: string;
buttonDisabled?: boolean;
}
4. Manage Internal State Minimally
const SearchForm = ({ onSubmit }) => {
const [query, setQuery] = useState('');
return (
<form onSubmit={() => onSubmit(query)}>
<Input value={query} onChange={(e) => setQuery(e.target.value)} />
<Button type="submit">Search</Button>
</form>
);
};
const SearchForm = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
fetchResults(query).then(setResults);
}, [query]);
};
Anti-Patterns to Avoid
1. Molecules Containing Molecules
import { FormField } from '../FormField';
import { SearchForm } from '../SearchForm';
import { FormField } from '@/components/molecules/FormField';
import { SearchForm } from '@/components/molecules/SearchForm';
2. Business Logic in Molecules
const SearchForm = ({ apiEndpoint }) => {
const handleSubmit = async (query) => {
const results = await fetch(`${apiEndpoint}?q=${query}`);
};
};
const SearchForm = ({ onSubmit }) => {
const handleSubmit = (query) => {
onSubmit(query);
};
};
3. Over-Abstraction
const IconWrapper = ({ icon }) => <Icon name={icon} />;
<Icon name="search" />
When to Use This Skill
- Combining atoms for specific functionality
- Creating reusable form components
- Building navigation elements
- Creating card and list components
- Establishing patterns for common UI combinations
Related Skills
atomic-design-fundamentals - Core methodology overview
atomic-design-atoms - Creating atomic components
atomic-design-organisms - Building complex organisms