A "use my current location" button that is right about why it didn't. It asks the device for a position and, when that is refused, works out which of four unrelated things the refusal actually was — because the browser reports all four with the same number — so what it puts on screen is advice that can work rather than advice that cannot. Reach for it wherever typing an address is the worst part of the page: the "find my address" step of a checkout, signup, delivery or booking form; a store, branch, ATM, pharmacy, restaurant or EV-charger finder; a weather, air-quality, pollen, tide or prayer-times view that ought to open on where you already are; the initial centre of a map; a check-in, attendance, inspection or field-service app; a taxi, ride or courier pickup point; local search, classifieds, jobs or property listings; a "near me" radius on events or dating; a shipping, delivery-window or tax estimate; and any first run that would otherwise begin by asking somebody which city they are in. Common asks it answers: "geolocation react", "use my current location button", "get user location react", "navigator.geolocation react", "useGeolocation hook", "react-geolocated alternative", "shadcn geolocation", "current location button shadcn", "getCurrentPosition react", "watchPosition react hook", "request location permission react", "geolocation permission denied react", "user denied geolocation how to ask again", "how to re-request location permission", "geolocation prompt not showing", "getCurrentPosition not working on localhost", "geolocation not working over http", "geolocation requires https", "getCurrentPosition hangs forever", "geolocation timeout default infinity", "location spinner never stops", "clearWatch react", "geolocation battery drain", "navigator.permissions.query geolocation", "check if location is blocked without prompting", "geolocation in iframe not working", "Permissions-Policy geolocation". The reason to install one rather than write it is that the platform answers four different questions with one number. The specification's "request a position" algorithm calls back with PERMISSION_DENIED when a Permissions Policy forbids the feature, again when the page is not a secure context, and again when the stored permission is "denied" — and a prompt the user closes without answering arrives the same way. The version everyone writes maps that code to "you have blocked location access, turn it back on in your browser settings", which is true in one of those cases and misleading in the other three. On the http staging box on an internal IP there is no setting to change and no prompt was ever shown. Inside an <iframe> without allow="geolocation", the same. And somebody who merely dismissed the dialog is told they blocked something they did not. So the code is never taken at face value. Two of the causes are settled before calling at all, which is also what stops a doomed request being made: the secure-context check — needed because the attribute is not [SecureContext] and so is present, and useless, on every http origin, which is why the usual feature detect passes there — and document.permissionsPolicy.allowsFeature("geolocation") where a browser has it. The rest is decided by reading the stored state with navigator.permissions.query, which answers without prompting: still "prompt" after a refusal means the dialog was dismissed, "granted" means the block came from above the user, and "denied" means it is the user's own. Only the first of those is offered a retry, because the specification is explicit that once the state is "denied", getCurrentPosition calls back immediately without prompting — a "Try again" button there cannot work, and returns the same error instantly for as long as the page stays open. What it offers instead is the browser's own site settings, and it subscribes to the PermissionStatus change event, so the moment somebody flips that switch the dead end clears itself and the button works again with no reload. Two more defaults are corrected on the way past. timeout is Infinity in the browser, so the hand-written version hangs — no success, no failure, spinner still turning — on a phone indoors, in a lift or in aeroplane mode; this one always sends a finite deadline. And that deadline does not cover everything, which is why the button separates "waiting for permission" from "finding your location": by the specification the time spent waiting for the document to become visible and for the permission to be answered is not included in timeout, so an unanswered dialog is an unbounded wait — correctly, because a person deciding whether to hand over their location is not a fault and must not be cut off and told it failed. Watch mode registers with watchPosition and hands it back with clearWatch on unmount, on clear() and before any restart, which is the leak with no symptom on screen: an abandoned watch keeps the location hardware awake for the life of the page, long after the map that wanted it was navigated away from. Official shadcn/ui has nothing here — geolocation, getCurrentPosition, watchPosition, clearWatch, coords, navigator.permissions and PermissionStatus appear nowhere in its components. Within pulld it is the one that has to ask for something: network-status works out whether the network is really there, wake-lock-toggle holds a resource the browser can take back silently, and this one handles the permission that can be refused for good. It sits naturally beside a map or an address form, and alongside country-select, phone-input or timezone-select on the same form. useGeolocation() is exported for a control of your own, handing back phase, position, failure, permission, isSupported, request() and clear(); isGeolocationSupported() is exported too and reports only that the API exists — deliberately not that it will work, which on http is a different question. The failure object carries a cause, the browser's own code, a retryable flag and wording you can override per cause, so a design of your own can draw the same distinctions. The message sits in an always-mounted polite live region, because a live region inserted together with its text is not reliably announced and would be silent for exactly the people relying on it. The button uses aria-disabled rather than disabled, so a refusal keeps its place in the tab order and can still explain itself, points at the message with aria-describedby, and carries aria-busy while it waits. Every colour is a shadcn token, so it follows light and dark, and the whole thing is one file.
pnpm dlx shadcn@latest add "https://pulld.pages.dev/r/geolocation-button.json"