# Alpine.js Patterns in Bookhoard
## Book Picker Modal Pattern (SSR + HTMX + Alpine.store)
For modals that need state persistence across HTMX swaps:
### TypeScript
```typescript
// web/src/bookPicker.ts
Alpine.store("bookPicker", {
  isOpen: false,
  selectedBooks: new Set<string>(),
  
  open() {
    this.isOpen = true;
    this.selectedBooks.clear();
    this.loadBooks();
  },
  
  close() {
    this.isOpen = false;
    this.selectedBooks.clear();
  },
  
  toggleBook(id: string) {
    if (this.selectedBooks.has(id)) {
      this.selectedBooks.delete(id);
    } else {
      this.selectedBooks.add(id);
    }
    // Trigger reactivity
    this.selectedBooks = new Set(this.selectedBooks);
  },
  
  isSelected(id: string): boolean {
    return this.selectedBooks.has(id);
  },
  
  get selectedCount(): number {
    return this.selectedBooks.size;
  }
});
Template
<!-- Open button -->
<button @click="$store.bookPicker.open()">Add Books</button>
<!-- Modal -->
<div x-show="$store.bookPicker.isOpen"
     x-transition
     @click.self="$store.bookPicker.close()"
     style="display: none;">
  
  <!-- Search with HTMX -->
  <input @input.debounce.300ms="searchBooks($el.value)"
         hx-get="/api/media-items/filtered"
         hx-target="#book-picker-grid"
         hx-include="#filter-form" />
  
  <!-- Book grid with checkboxes -->
  <div id="book-picker-grid" x-init="loadBooks()">
    <!-- Server returns HTML with checkboxes -->
  </div>
  
  <!-- Selected count -->
  <span x-text="$store.bookPicker.selectedCount"></span>
  
  <!-- Submit -->
  <button @click="$store.bookPicker.submit()">Add Selected</button>
</div>
Key Principles
- ✅ Alpine.store persists state across HTMX DOM swaps
- ✅ Server returns HTML with checkbox :checked attributes
- ✅ No client-side rendering or manual DOM manipulation
- ✅ HTMX handles dynamic content updates
- ✅ Alpine manages UI state only
Dropdown Pattern (Local x-data)
<div x-data="{ dropdownOpen: false }">
  <button @click="dropdownOpen = !dropdownOpen">Toggle</button>
  <div x-show="dropdownOpen"
       @click.outside="dropdownOpen = false"
       x-transition
       style="display: none;">
    <!-- Dropdown content -->
  </div>
</div>
Best Practices
- ✅ State lives in template (x-data)
- ✅ UI updates automatically (x-show)
- ✅ No manual DOM manipulation in TypeScript
- ✅ Pure business logic only in TypeScript functions
- ✅ Use Alpine.store for global state
- ✅ Use local x-data for component state
- ✅ Use x-init for setup only (no data fetch for SSR pages)
- ❌ Never use classList in TypeScript
- ❌ Never use getElementById for show/hide
- ❌ Never fetch data in x-init (for SSR pages)
