| name | first-aid-reference |
| description | Offline-capable PWA providing searchable first aid guides with step-by-step instructions. Use when you need to look up first aid steps, search for guides by condition or symptom, manage bookmarks, add emergency contacts, or understand the app's offline capabilities. Triggers include "first aid", "CPR steps", "burn treatment", "choking", "emergency guide", or any query about first aid procedures. |
first-aid-reference
A Progressive Web App (PWA) that provides offline-capable first aid guides. All content is static JSON bundled at build time. No account required, no data collected.
When to use
- Looking up step-by-step first aid instructions for a specific condition
- Searching for guides by symptom, keyword, or category
- Checking whether a condition is classed as EMERGENCY, URGENT, or ROUTINE
- Managing bookmarked guides
- Adding or reviewing emergency contacts
- Installing the app to a mobile device's home screen
- Understanding what is and is not cached for offline use
Base URL
This is a client-side SPA with no API server. All functionality runs in the browser.
http://localhost:5173 (dev server)
http://localhost:4173 (preview build)
After a production build, serve from any static file host:
pnpm build
pnpm preview
Navigation
| Path | Page |
|---|
| / | Home - category grid and recent guides |
| /category/:id | All guides in a category |
| /article/:id | Full guide with steps |
| /search?q=query | Search results |
| /bookmarks | Saved guides |
| /settings | Display preferences |
Content Categories
| id | name | emergency level |
|---|
| cardiac | Cardiac Arrest | all emergency |
| breathing | Breathing Problems | all emergency |
| bleeding | Bleeding | mixed |
| burns | Burns | mixed |
| choking | Choking | all emergency |
| fractures | Fractures | routine |
| poisoning | Poisoning | emergency |
| shock | Shock | all emergency |
| allergic | Allergic Reaction | emergency |
| bites | Bites and Stings | mixed |
Offline Capability
The service worker (Workbox via vite-plugin-pwa) precaches all build output on first load. This includes:
- All React component bundles
articles.json and categories.json
- PWA icons and manifest
After the first visit, the app works with no internet connection.
Deploying the PWA
Build
pnpm install
pnpm build
The dist/ folder contains a fully static site including:
index.html
sw.js (service worker)
manifest.webmanifest
- All assets
Deploy to Netlify
pnpm add -g netlify-cli
netlify deploy --prod --dir=dist
Add a _headers file in public/ for CSP headers:
/*
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Deploy to Vercel
pnpm add -g vercel
vercel --prod
Deploy with Docker (static nginx)
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
nginx.conf must include try_files $uri $uri/ /index.html; for SPA routing.
Deploy to a static file server
cd dist && python3 -m http.server 8080
npx serve dist -p 8080
Customizing content: adding guides
Guides are stored in src/data/articles.json. Each guide follows this structure:
{
"id": "my-guide-id",
"category": "burns",
"title": "My Guide Title",
"emergency": false,
"steps": [
{ "order": 1, "text": "Step one instruction.", "note": "Optional clarifying note." }
],
"doNot": ["Do not do this.", "Do not do that."],
"symptoms": ["Symptom one", "Symptom two"],
"whenToCall": ["If condition X occurs"],
"tags": ["keyword1", "keyword2"],
"relatedIds": ["other-guide-id"]
}
After editing articles.json, run pnpm build to rebuild the app.
To validate content structure before building:
pnpm tsx scripts/validate-content.ts
Configuring emergency contacts
Emergency contacts are stored in localStorage under the key first-aid-contacts. They can be managed from the Contacts page (/contacts) in the app. Contacts are never transmitted to any server.
JSON.parse(localStorage.getItem('first-aid-contacts') ?? '[]')
Installing on iOS
iOS Safari does not support the beforeinstallprompt event. Manual install instructions:
- Open the app URL in Safari
- Tap the Share button (box with arrow)
- Scroll down and tap "Add to Home Screen"
- Tap "Add"
The app will appear as a standalone icon and open without browser chrome.
Installing on Android
Chrome and Edge on Android support automatic install prompts. The InstallPrompt component captures the beforeinstallprompt event and shows an install banner.
Users can also install manually:
- Open in Chrome
- Tap the three-dot menu
- Tap "Add to Home screen"
Accessibility features
- All interactive elements are keyboard-accessible with visible focus ring (2px solid
--border-focus)
- Step numbers have
aria-label="Step N of M"
- Emergency banner uses
role="alert" and aria-live="assertive"
- SeverityBadge has
role="status" with full text
- High contrast mode increases border widths and font weights (toggled in Settings)
- Minimum tap target size: 44x44px on mobile
- Color is never the sole indicator of status - text labels always accompany color
Environment variables (build time only)
| Variable | Default | Description |
|---|
| VITE_APP_NAME | first-aid-reference | App display name in manifest |
| VITE_APP_VERSION | 1.0.0 | Shown in Settings page |
Medical disclaimer
Every article page displays:
"This information is for general educational purposes only. Always call emergency services (000, 112, or your local emergency number) for life-threatening situations. This app does not replace professional medical advice."
This disclaimer is rendered by DisclaimerBanner.tsx and must never be removed or hidden.