openrouter-model-picker
Version:
Third-party React component for OpenRouter model selection
391 lines (301 loc) • 11.3 kB
Markdown
# OpenRouter Model Picker
A React component that gives you an **in-app model selector** for the [OpenRouter API](https://openrouter.ai/docs/api-reference/list-available-models). Browse, filter, and select from 325+ AI models with real-time cost comparison and spec viewing - no more alt-tabbing during development.
*This is an unofficial tool created by a user of OpenRouter's service - not affiliated with or endorsed by OpenRouter.*
[](https://badge.fury.io/js/openrouter-model-picker)
[](https://www.npmjs.com/package/openrouter-model-picker)

## 🎯 Why This Exists
**LLM model selection is a moving target.** When building AI-powered applications, choosing the right model involves constant trade-offs between cost, quality, speed, and capabilities. This is what I call finding **Product-Model Fit** - the model that best serves your product at each stage of development.
> 📖 **Read more:** [Product Model Fit with OpenRouter Model Picker](https://dannyshmueli.com/2025/06/21/Product-Model-Fit-with-OpenRouter-Model-Picker/) - A deeper dive into the concept and development story behind this component.
As a developer, I found myself constantly alt-tabbing between the OpenRouter website and my code to explore available models, compare pricing, and check capabilities. This became especially tedious when:
- **Building prototypes** where I needed to quickly test different models
- **Optimizing costs** by comparing pricing across providers and model tiers
- **Matching capabilities** to specific use cases (vision, context length, etc.)
- **Avoiding analysis paralysis** with 325+ model options
Rather than hardcoding a few model options or losing momentum switching between tabs, this component brings the entire OpenRouter model catalog directly into your application. Make informed decisions about model selection based on real-time pricing, capabilities, and performance characteristics - all within your app's interface.
**Perfect for:**
- Finding **Product-Model Fit** during development and iteration
- Developer tools and AI playgrounds
- Cost optimization dashboards
- Multi-model AI applications
- Educational tools teaching about different LLMs
## ✨ Features
- 🔍 **Smart Filtering**: Real-time search across model names, providers, descriptions, and features
- 💰 **Cost Awareness**: Color-coded pricing tiers and detailed cost breakdowns
- 📊 **Performance Metrics**: Context window and capability information
- ♿ **Accessibility**: Full keyboard navigation, screen reader support, ARIA compliance
- 📱 **Responsive**: Optimized for desktop, tablet, and mobile devices
- 🎨 **Customizable**: Light/dark themes, custom sizing, and CSS classes
- ⚡ **Performance**: Debounced search, memoized operations, efficient rendering
- 🔒 **Type Safe**: Full TypeScript support with comprehensive type definitions
## 🚀 Installation
```bash
npm install openrouter-model-picker
# or
yarn add openrouter-model-picker
# or
pnpm add openrouter-model-picker
```
### Installing from GitHub (Development)
```bash
npm install github:dannyshmueli/openrouter-model-picker
```
## 📦 Basic Usage
```tsx
import React, { useState } from 'react'
import { ModelChooserModal } from 'openrouter-model-picker'
import 'openrouter-model-picker/styles'
function App() {
const [selectedModel, setSelectedModel] = useState('openai/gpt-4o-mini')
const [isModalOpen, setIsModalOpen] = useState(false)
return (
<>
<button onClick={() => setIsModalOpen(true)}>
Choose Model: {selectedModel}
</button>
<ModelChooserModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
selectedModel={selectedModel}
onModelChange={setSelectedModel}
/>
</>
)
}
```
## 🔧 Advanced Usage
### Using the Selected Model
```tsx
import React, { useState } from 'react'
import { ModelChooserModal } from 'openrouter-model-picker'
import 'openrouter-model-picker/styles'
function AdvancedApp() {
const [selectedModel, setSelectedModel] = useState('openai/gpt-4o-mini')
const [isModalOpen, setIsModalOpen] = useState(false)
const handleApiCall = async () => {
if (!selectedModel) return
try {
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: selectedModel, // Use the selected model ID
messages: [
{ role: 'user', content: 'Hello, how are you?' }
]
})
})
const data = await response.json()
console.log('Response:', data)
} catch (error) {
console.error('API Error:', error)
}
}
return (
<div>
<button onClick={() => setIsModalOpen(true)}>
Choose Model
</button>
{selectedModel && (
<div>
<p>Selected: {selectedModel}</p>
<button onClick={handleApiCall}>
Make API Call
</button>
</div>
)}
<ModelChooserModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
selectedModel={selectedModel}
onModelChange={setSelectedModel}
maxHeight="90vh" // Custom height
maxWidth="1200px" // Custom width
className="my-modal" // Custom CSS class
/>
</div>
)
}
```
### With Custom Fallback Data
```tsx
import { ModelChooserModal, ModelInfo } from 'openrouter-model-picker'
import 'openrouter-model-picker/styles'
const fallbackModels: ModelInfo[] = [
{
id: 'openai/gpt-4o-mini',
name: 'GPT-4o Mini',
provider: 'OpenAI',
costTier: 'low',
description: 'Fast and affordable multimodal model',
features: ['Vision', 'Fast'],
pricing: { input: 0.00015, output: 0.0006, currency: 'USD' },
context: 128000,
multimodal: true
}
]
function AppWithFallback() {
return (
<ModelChooserModal
// ... other props
fallbackModels={fallbackModels}
/>
)
}
```
## 📋 API Reference
### ModelChooserModalProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isOpen` | `boolean` | - | Controls modal visibility |
| `onClose` | `() => void` | - | Called when modal is closed |
| `selectedModel` | `string` | - | Currently selected model ID |
| `onModelChange` | `(modelId: string) => void` | - | Called when model selection changes |
| `fallbackModels` | `ModelInfo[]` | - | Fallback model data |
| `maxHeight` | `string` | `'80vh'` | Maximum modal height |
| `maxWidth` | `string` | - | Maximum modal width |
| `className` | `string` | - | Custom CSS class |
### ModelInfo Interface
```typescript
interface ModelInfo {
id: string // Model ID (e.g., "openai/gpt-4o")
name: string // Display name
provider: string // Provider name
costTier: 'free' | 'low' | 'medium' | 'high'
description: string // Model description
features?: string[] // Capability tags
pricing?: {
input: number // Input token price
output: number // Output token price
currency: string // Currency code
}
context?: number // Context window size
multimodal?: boolean // Supports images/files
}
```
## 🎨 Theming and Customization
### CSS Custom Properties
```css
.openrouter-modal {
--or-primary-color: #3b82f6;
--or-primary-hover: #2563eb;
--or-background: #ffffff;
--or-text: #111827;
--or-text-secondary: #6b7280;
--or-border: #e5e7eb;
}
/* Dark theme */
.openrouter-modal.dark {
--or-background: #1f2937;
--or-text: #f9fafb;
--or-text-secondary: #9ca3af;
--or-border: #374151;
}
```
### Cost Tier Colors
- **Free**: `#10b981` (green)
- **Low**: `#3b82f6` (blue)
- **Medium**: `#f59e0b` (amber)
- **High**: `#ef4444` (red)
## 🔌 Hooks and Utilities
### useModelData Hook
```tsx
import { useModelData } from 'openrouter-model-picker'
function CustomComponent() {
const { models, loading, error, refresh } = useModelData()
// Use models data directly
return (
<div>
{models.map(model => (
<div key={model.id}>{model.name}</div>
))}
</div>
)
}
```
### useFiltering Hook
```tsx
import { useFiltering } from 'openrouter-model-picker'
function FilteredList({ models }) {
const {
filteredModels,
filterState,
updateFilter,
clearFilters
} = useFiltering(models)
return (
<div>
<input
type="text"
onChange={(e) => updateFilter('searchTerm', e.target.value)}
placeholder="Search models..."
/>
{/* Render filtered models */}
</div>
)
}
```
## ♿ Accessibility
The component is built with accessibility in mind:
- **Keyboard Navigation**: Full support for keyboard-only users
- **Screen Readers**: ARIA labels and semantic HTML structure
- **Focus Management**: Proper focus trapping within the modal
- **High Contrast**: Support for high contrast mode
- **Reduced Motion**: Respects `prefers-reduced-motion` setting
## 📱 Responsive Design
### Breakpoints
- **Mobile**: `< 768px` - Simplified layout, touch-optimized
- **Tablet**: `768px - 1024px` - Condensed columns
- **Desktop**: `> 1024px` - Full table layout
### Mobile Adaptations
- Card-based layout instead of table
- Touch-friendly filter controls
- Optimized modal sizing
## 🔒 Error Handling
The component gracefully handles various error scenarios:
- **API Unavailable**: Falls back to cached or provided fallback data
- **Network Errors**: Shows retry option with error details
- **Rate Limiting**: Displays appropriate messages with retry timers
- **Empty Results**: Clear messaging with filter reset options
## 📊 Performance
### Optimization Features
- **Debounced Search**: 300ms delay to reduce excessive filtering
- **Memoized Operations**: Optimized filtering and sorting
- **Efficient Rendering**: Optimized React components
- **Small Bundle Size**: 44.3 kB package size
## 🛠️ Development
### Setup
```bash
git clone https://github.com/dannyshmueli/openrouter-model-picker
cd openrouter-model-picker
npm install
```
### Development Server
```bash
npm run dev
```
### Build
```bash
npm run build
```
### Type Check
```bash
npm run type-check
```
## 📄 License
MIT License - see [LICENSE](LICENSE) file for details.
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## 📞 Support
- 📖 [GitHub Repository](https://github.com/dannyshmueli/openrouter-model-picker)
- 🐛 [Issue Tracker](https://github.com/dannyshmueli/openrouter-model-picker/issues)
- 📦 [npm Package](https://www.npmjs.com/package/openrouter-model-picker)
## 🙏 Acknowledgments
- [OpenRouter](https://openrouter.ai) for the AI model API
- [Lucide React](https://lucide.dev) for the icon set
- The React community for best practices and patterns
---
Built with ❤️ for the AI and Vibe coding community