Build a Prayer Times Component for Vue.js
This post shows you how to build a reusable Vue 3 component that fetches prayer times from UmmahAPI, detects the user's location automatically, and counts down to the next prayer in real time.
What You Need
You need a Vue 3 project. The examples below use the Composition API with <script setup>. No extra libraries are required for the API calls since we use the native fetch API. If you want to scaffold a new project quickly, use Vite:
npm create vite@latest prayer-app -- --template vue
cd prayer-app
npm install
npm run dev
That is all you need to follow along. The component we build will be a single .vue file you can drop into any project.
The UmmahAPI Prayer Times Endpoint
The UmmahAPI prayer times endpoint is a GET request. You pass a latitude and longitude and it returns today's prayer schedule. Here is a raw example:
// Replace lat and lng with real coordinates
fetch('https://ummahapi.com/api/prayer-times?lat=21.42&lng=39.82')
.then(res => res.json())
.then(data => console.log(data));
The response includes these fields: Fajr, Sunrise, Dhuhr, Asr, Maghrib, and Isha. Each value is a time string like "05:12". The API does not require a key for up to 5,000 requests per 15 minutes. For production, grab a free key and add it as a query parameter ?key=YOUR_KEY.
Tip. Check the full API docs to see the 23 available calculation methods. You can pass &method=2 or similar to switch between ISNA, MWL, Egyptian, and other methods.
Detecting the User's Location
The browser's Geolocation API gives us coordinates without any extra setup. We call it inside Vue's onMounted hook so it runs after the component appears on screen. Here is a small composable you can put in src/composables/useGeolocation.js:
import { ref } from 'vue'
export function useGeolocation() {
const lat = ref(null)
const lng = ref(null)
const error = ref(null)
const loading = ref(false)
function getPosition() {
if (!navigator.geolocation) {
error.value = 'Geolocation is not supported by your browser.'
return
}
loading.value = true
navigator.geolocation.getCurrentPosition(
(pos) => {
lat.value = pos.coords.latitude
lng.value = pos.coords.longitude
loading.value = false
},
(err) => {
error.value = err.message
loading.value = false
}
)
}
return { lat, lng, error, loading, getPosition }
}
This composable is reusable. Any component in your app can call useGeolocation() and get the same refs.
Fetching Prayer Times
Now build a second composable that takes latitude and longitude and calls UmmahAPI. Put this in src/composables/usePrayerTimes.js:
import { ref, watch } from 'vue'
const PRAYER_NAMES = ['Fajr', 'Sunrise', 'Dhuhr', 'Asr', 'Maghrib', 'Isha']
export function usePrayerTimes(lat, lng) {
const times = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchTimes() {
if (!lat.value || !lng.value) return
loading.value = true
error.value = null
try {
const url = `https://ummahapi.com/api/prayer-times?lat=${lat.value}&lng=${lng.value}`
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP error: ${res.status}`)
const data = await res.json()
times.value = PRAYER_NAMES.map(name => ({
name,
time: data.data[name] ?? data[name]
}))
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
watch([lat, lng], fetchTimes)
return { times, loading, error, fetchTimes }
}
The watch on [lat, lng] means the fetch fires automatically once the geolocation composable updates those refs. You do not need to wire them up manually.
Countdown to the Next Prayer
This is where it gets useful. We parse each prayer time string into a full Date object for today, find the first one that is still in the future, then run a one-second interval to show the remaining time.
import { ref, computed, onUnmounted } from 'vue'
export function useCountdown(times) {
const countdown = ref('')
const nextPrayer = ref(null)
let timer = null
function timeStringToDate(timeStr) {
const [hours, minutes] = timeStr.split(':').map(Number)
const d = new Date()
d.setHours(hours, minutes, 0, 0)
return d
}
function pad(n) {
return String(n).padStart(2, '0')
}
function tick() {
const now = new Date()
const upcoming = times.value
.filter(p => p.time)
.map(p => ({ name: p.name, date: timeStringToDate(p.time) }))
.find(p => p.date > now)
if (!upcoming) {
countdown.value = 'No more prayers today'
nextPrayer.value = null
return
}
nextPrayer.value = upcoming.name
const diff = upcoming.date - now
const h = Math.floor(diff / 3600000)
const m = Math.floor((diff % 3600000) / 60000)
const s = Math.floor((diff % 60000) / 1000)
countdown.value = `${pad(h)}:${pad(m)}:${pad(s)}`
}
function startTimer() {
if (timer) clearInterval(timer)
tick()
timer = setInterval(tick, 1000)
}
function stopTimer() {
if (timer) clearInterval(timer)
}
onUnmounted(stopTimer)
return { countdown, nextPrayer, startTimer, stopTimer }
}
Call startTimer() after the prayer times load. The onUnmounted call cleans up the interval when the component is removed from the page, so you will not have memory leaks.
Tip. Prayer time strings from the API use 24-hour format, so "13:30" is 1:30 PM. The timeStringToDate function above handles this correctly because it passes hours directly to setHours.
Putting It All Together in One Component
Now combine all three composables into a single PrayerTimes.vue file. This is the full component:
<!-- src/components/PrayerTimes.vue -->
<script setup>
import { onMounted, watch } from 'vue'
import { useGeolocation } from '../composables/useGeolocation.js'
import { usePrayerTimes } from '../composables/usePrayerTimes.js'
import { useCountdown } from '../composables/useCountdown.js'
const { lat, lng, error: geoError, loading: geoLoading, getPosition } = useGeolocation()
const { times, loading, error, fetchTimes } = usePrayerTimes(lat, lng)
const { countdown, nextPrayer, startTimer } = useCountdown(times)
onMounted(getPosition)
watch(times, (newTimes) => {
if (newTimes.length) startTimer()
})
</script>
<template>
<div class="prayer-widget">
<div v-if="geoLoading || loading" class="status">Detecting location...</div>
<div v-else-if="geoError || error" class="error">
{{ geoError || error }}
</div>
<div v-else-if="times.length">
<div class="countdown-block">
<p class="next-label">Next: <strong>{{ nextPrayer }}</strong></p>
<p class="countdown">{{ countdown }}</p>
</div>
<ul class="times-list">
<li
v-for="prayer in times"
:key="prayer.name"
:class="{ active: prayer.name === nextPrayer }"
>
<span class="prayer-name">{{ prayer.name }}</span>
<span class="prayer-time">{{ prayer.time }}</span>
</li>
</ul>
</div>
<div v-else class="status">Allow location access to see prayer times.</div>
</div>
</template>
The :class="{ active: prayer.name === nextPrayer }" binding highlights the upcoming prayer row automatically. Add whatever CSS you like for the .active class, for example a green left border or a bold font weight.
Using the Component in Your App
Drop PrayerTimes.vue into any page. Here is a simple App.vue example:
<!-- src/App.vue -->
<script setup>
import PrayerTimes from './components/PrayerTimes.vue'
</script>
<template>
<main>
<h1>My Islamic App</h1>
<PrayerTimes />
</main>
</template>
The component handles everything internally. The parent does not need to pass any props. When the user accepts the location prompt, the times load and the countdown starts on its own.
Fetching a Full Month of Times
Sometimes you want to show a monthly schedule, not just today. The API supports that with month and year query parameters. Here is how you fetch it:
async function fetchMonthlyTimes(lat, lng, month, year) {
const url = `https://ummahapi.com/api/prayer-times?lat=${lat}&lng=${lng}&month=${month}&year=${year}`
const res = await fetch(url)
const data = await res.json()
return data // array of daily prayer time objects
}
// Example: get October 2026 for Mecca
fetchMonthlyTimes(21.42, 39.82, 10, 2026).then(console.log)
You could build a calendar view from this data, letting users swipe between days. Each item in the returned array has the same Fajr, Sunrise, Dhuhr, Asr, Maghrib, and Isha fields.
Styling Tips
Here is a minimal CSS block to make the widget look clean. Add this inside a <style scoped> block in PrayerTimes.vue:
/* PrayerTimes.vue <style scoped> */
.prayer-widget {
font-family: Inter, sans-serif;
max-width: 320px;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 1.25rem;
}
.countdown-block {
text-align: center;
margin-bottom: 1rem;
}
.countdown {
font-size: 2rem;
font-weight: 700;
letter-spacing: 0.05em;
color: #16a34a;
}
.times-list {
list-style: none;
padding: 0;
margin: 0;
}
.times-list li {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
border-bottom: 1px solid #f3f4f6;
}
.times-list li.active {
font-weight: 600;
color: #16a34a;
border-left: 3px solid #16a34a;
padding-left: 0.5rem;
}
This gives you a clean card layout with green highlights for the next prayer. Adjust the colors to match your app's design system.
FAQ
What API endpoint do I use to get prayer times in Vue?
Use GET https://ummahapi.com/api/prayer-times?lat=YOUR_LAT&lng=YOUR_LNG. It returns today's Fajr, Sunrise, Dhuhr, Asr, Maghrib, and Isha times as strings. See the prayer times API page for full details.
Does the prayer times Vue component need an API key?
No API key is required for basic use. UmmahAPI allows 5,000 requests per 15 minutes without a key. For production apps, register for a free key to get unlimited requests.
How do I get the user's location in a Vue component?
Use navigator.geolocation.getCurrentPosition() inside the onMounted lifecycle hook. Store the latitude and longitude in reactive refs, then pass them to the UmmahAPI endpoint. The composable in this post shows exactly how to do that.
How do I build a countdown to the next prayer in Vue?
Parse each prayer time string into a Date object for today, compare them to new Date() to find the next upcoming prayer, then run setInterval every 1000ms to recompute the difference. Always clear the interval in onUnmounted to avoid memory leaks.
Start building with UmmahAPI today
UmmahAPI is free and gives you prayer times, Quran data, Hadith, Qibla direction, and more with a single base URL.
Read the docs