Claude
Skills
Sign in
Back

atomic-design-molecules

Included with Lifetime
$97 forever

Use when composing atoms into molecule components like form fields, search bars, and card headers. Molecules are functional groups of atoms.

Design

What this skill does


# 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

```typescript
// molecules/FormField/FormField.tsx
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 {
  /** Field label */
  label: string;
  /** Unique field identifier */
  name: string;
  /** Help text below input */
  helpText?: string;
  /** Error message */
  error?: string;
  /** Required field indicator */
  required?: boolean;
}

export const FormField = React.forwardRef<HTMLInputElement, FormFieldProps>(
  (
    {
      label,
      name,
      helpText,
      error,
      required = false,
      id,
      className,
      ...inputProps
    },
    ref
  ) => {
    const fieldId = id || `field-${name}`;
    const helpTextId = helpText ? `${fieldId}-help` : undefined;
    const errorId = error ? `${fieldId}-error` : undefined;

    const describedBy = [helpTextId, errorId].filter(Boolean).join(' ') || undefined;

    return (
      <div className={`${styles.field} ${className || ''}`}>
        <Label htmlFor={fieldId} required={required} disabled={inputProps.disabled}>
          {label}
        </Label>

        <Input
          ref={ref}
          id={fieldId}
          name={name}
          hasError={!!error}
          aria-describedby={describedBy}
          aria-required={required}
          {...inputProps}
        />

        {helpText && !error && (
          <Text id={helpTextId} size="sm" color="muted" className={styles.helpText}>
            {helpText}
          </Text>
        )}

        {error && (
          <Text id={errorId} size="sm" color="danger" className={styles.error} role="alert">
            {error}
          </Text>
        )}
      </div>
    );
  }
);

FormField.displayName = 'FormField';
```

```css
/* molecules/FormField/FormField.module.css */
.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

```typescript
// molecules/SearchForm/SearchForm.tsx
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 text */
  placeholder?: string;
  /** Initial search value */
  defaultValue?: string;
  /** Submit handler */
  onSubmit: (query: string) => void;
  /** Change handler for live search */
  onChange?: (query: string) => void;
  /** Loading state */
  isLoading?: boolean;
  /** Size variant */
  size?: 'sm' | 'md' | 'lg';
  /** Show clear button */
  clearable?: boolean;
}

export const SearchForm: React.FC<SearchFormProps> = ({
  placeholder = 'Search...',
  defaultValue = '',
  onSubmit,
  onChange,
  isLoading = false,
  size = 'md',
  clearable = true,
}) => {
  const [query, setQuery] = useState(defaultValue);

  const handleChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const value = e.target.value;
      setQuery(value);
      onChange?.(value);
    },
    [onChange]
  );

  const handleSubmit = useCallback(
    (e: React.FormEvent) => {
      e.preventDefault();
      onSubmit(query.trim());
    },
    [onSubmit, query]
  );

  const handleClear = useCallback(() => {
    setQuery('');
    onChange?.('');
  }, [onChange]);

  return (
    <form className={styles.form} onSubmit={handleSubmit} role="search">
      <Input
        type="search"
        value={query}
        onChange={handleChange}
        placeholder={placeholder}
        size={size}
        leftAddon={<Icon name="search" size="sm" />}
        rightAddon={
          clearable && query ? (
            <button
              type="button"
              onClick={handleClear}
              className={styles.clearButton}
              aria-label="Clear search"
            >
              <Icon name="x" size="sm" />
            </button>
          ) : undefined
        }
        aria-label="Search query"
      />
      <Button type="submit" size={size} isLoading={isLoading}>
        Search
      </Button>
    </form>
  );
};

SearchForm.displayName = 'SearchForm';
```

```css
/* molecules/SearchForm/SearchForm.module.css */
.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

```typescript
// molecules/MediaObject/MediaObject.tsx
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 {
  /** Avatar image source */
  avatarSrc?: string;
  /** Avatar alt text */
  avatarAlt: string;
  /** Avatar initials fallback */
  avatarInitials?: string;
  /** Avatar size */
  avatarSize?: AvatarProps['size'];
  /** Primary text/title */
  title: React.ReactNode;
  /** Secondary text/subtitle */
  subtitle?: React.ReactNode;
  /** Additional metadata */
  meta?: React.ReactNode;
  /** Right-aligned action element */
  action?: React.ReactNode;
  /** Alignment of content */
  align?: 'top' | 'center' | 'bottom';
  /** Additional class name */
  className?: string;
}

export const MediaObject: React.FC<MediaObjectProps> = ({
  avatarSrc,
  avatarAlt,
  avatarInitials,
  avatarSize = 'md',
  title,
  subtitle,
  meta,
  action,
  align = 'center',
  className,
}) => {
  const classNames = [styles.mediaObject, styles[`align-${align}`], className]
    .filter(Boolean)
    .join(' ');

  return (
    <div className={classNames}>
      <Avatar
        src={avatarSrc}
        alt={avatarAlt}
        initials={avatarInitials}
        size={avatarSize}
      />

      <div className={styles.content}>
        <div className={styles.title}>{title}</div>
        {subtitle && (
          <Text size="sm" color="muted" className={styles.subtitle}>
            {subtitle}
          </Text>
        )}
        {meta && (
          <Text size="xs" color="muted" className={styles.meta}>
            {m

Related in Design