diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.css b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.css
index 21ad1759..eec17ded 100644
--- a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.css
+++ b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.css
@@ -3,7 +3,7 @@
  * or the screen reader users as the popup behavior hint
  * Inspired by https://gist.github.com/ffoodd/000b59f431e3e64e4ce1a24d5bb36034
  */
-.popup-close-message {
+.r6o-popup-sr-only {
   border: 0 !important;
   clip: rect(1px, 1px, 1px, 1px);
   -webkit-clip-path: inset(50%);
@@ -17,8 +17,8 @@
   white-space: nowrap;
 }
 
-.popup-close-message:focus,
-.popup-close-message:active {
+.r6o-popup-sr-only:focus,
+.r6o-popup-sr-only:active {
   clip: auto;
   -webkit-clip-path: none;
   clip-path: none;
diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx
index d001400e..59170bc4 100644
--- a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx
+++ b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx
@@ -1,4 +1,7 @@
-import React, { PointerEvent, ReactNode, useCallback, useEffect, useState } from 'react';
+import { PointerEvent, ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
+import { useAnnotator, useSelection } from '@annotorious/react';
+import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator';
+import { isMobile } from './isMobile';
 import {
   autoUpdate,
   flip,
@@ -13,13 +16,12 @@ import {
   useRole
 } from '@floating-ui/react';
 
-import { useAnnotator, useSelection } from '@annotorious/react';
-import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator';
-
 import './TextAnnotatorPopup.css';
 
 interface TextAnnotationPopupProps {
 
+  ariaCloseWarning?: string;
+
   popup(props: TextAnnotationPopupContentProps): ReactNode;
 
 }
@@ -39,24 +41,18 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
   const r = useAnnotator<TextAnnotator>();
 
   const { selected, event } = useSelection<TextAnnotation>();
+
   const annotation = selected[0]?.annotation;
 
   const [isOpen, setOpen] = useState(selected?.length > 0);
 
-  const handleClose = () => {
-    r?.cancelSelected();
-  };
-
   const { refs, floatingStyles, update, context } = useFloating({
-    placement: 'top',
+    placement: isMobile() ? 'bottom' : 'top',
     open: isOpen,
     onOpenChange: (open, _event, reason) => {
-      setOpen(open);
-
-      if (!open) {
-        if (reason === 'escape-key' || reason === 'focus-out') {
-          r?.cancelSelected();
-        }
+      if (!open && (reason === 'escape-key' || reason === 'focus-out')) {
+        setOpen(open);
+        r?.cancelSelected();
       }
     },
     middleware: [
@@ -69,26 +65,20 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
   });
 
   const dismiss = useDismiss(context);
+
   const role = useRole(context, { role: 'dialog' });
-  const { getFloatingProps } = useInteractions([dismiss, role]);
 
-  const selectedKey = selected.map(a => a.annotation.id).join('-');
-  useEffect(() => {
-    // Ignore all selection changes except those accompanied by a user event.
-    if (selected.length > 0 && event) {
-      setOpen(event.type === 'pointerup' || event.type === 'keydown');
-    }
-  }, [selectedKey, event]);
+  const { getFloatingProps } = useInteractions([dismiss, role]);
 
   useEffect(() => {
-    // Close the popup if the selection is cleared
-    if (selected.length === 0 && isOpen) {
-      setOpen(false);
-    }
-  }, [isOpen, selectedKey]);
+    setOpen(selected.length > 0);
+  }, [selected.map(a => a.annotation.id).join('-')]);
 
   useEffect(() => {
     if (isOpen && annotation) {
+      // Extra precaution - shouldn't normally happen
+      if (!annotation.target.selector || annotation.target.selector.length < 1) return;
+
       const {
         target: {
           selector: [{ range }]
@@ -96,21 +86,14 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
       } = annotation;
 
       refs.setPositionReference({
-        getBoundingClientRect: range.getBoundingClientRect.bind(range),
-        getClientRects: range.getClientRects.bind(range)
+        getBoundingClientRect: () => range.getBoundingClientRect(),
+        getClientRects: () => range.getClientRects()
       });
     } else {
-      // Don't leave the reference depending on the previously selected annotation
       refs.setPositionReference(null);
     }
   }, [isOpen, annotation, refs]);
 
-  // Prevent text-annotator from handling the irrelevant events triggered from the popup
-  const getStopEventsPropagationProps = useCallback(
-    () => ({ onPointerUp: (event: PointerEvent<HTMLDivElement>) => event.stopPropagation() }),
-    []
-  );
-
   useEffect(() => {
     const config: MutationObserverInit = { attributes: true, childList: true, subtree: true };
 
@@ -125,21 +108,27 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
     };
   }, [update]);
 
+  // Prevent text-annotator from handling the irrelevant events triggered from the popup
+  const getStopEventsPropagationProps = useCallback(
+    () => ({ onPointerUp: (event: PointerEvent<HTMLDivElement>) => event.stopPropagation() }),
+    []
+  );
+
+  // Don't shift focus to the floating element if selected via keyboard or on mobile.
+  const initialFocus = useMemo(() => {
+    return (event?.type === 'keyup' || event?.type === 'contextmenu' || isMobile()) ? -1 : 0;
+  }, [event]);
+
+  const onClose = () => r?.cancelSelected();
+
   return isOpen && selected.length > 0 ? (
     <FloatingPortal>
       <FloatingFocusManager
         context={context}
         modal={false}
         closeOnFocusOut={true}
-        initialFocus={
-          /**
-           * Don't shift focus to the floating element
-           * when the selection performed with the keyboard
-           */
-          event?.type === 'keydown' ? -1 : 0
-        }
         returnFocus={false}
-      >
+        initialFocus={initialFocus}>
         <div
           className="annotation-popup text-annotation-popup not-annotatable"
           ref={refs.setFloating}
@@ -152,13 +141,12 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
             event
           })}
 
-          {/* It lets keyboard/sr users to know that the dialog closes when they focus out of it */}
-          <button className="popup-close-message" onClick={handleClose}>
-            This dialog closes when you leave it.
+          <button className="r6o-popup-sr-only" aria-live="assertive" onClick={onClose}>
+            {props.ariaCloseWarning || 'Click or leave this dialog to close it.'}
           </button>
         </div>
       </FloatingFocusManager>
     </FloatingPortal>
   ) : null;
 
-};
+}
\ No newline at end of file
diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup/isMobile.ts b/packages/text-annotator-react/src/TextAnnotatorPopup/isMobile.ts
new file mode 100644
index 00000000..2cbde7c3
--- /dev/null
+++ b/packages/text-annotator-react/src/TextAnnotatorPopup/isMobile.ts
@@ -0,0 +1,17 @@
+// https://stackoverflow.com/questions/21741841/detecting-ios-android-operating-system
+export const isMobile = () => {
+  // @ts-ignore
+  var userAgent: string = navigator.userAgent || navigator.vendor || window.opera;
+
+  if (/android/i.test(userAgent)) 
+    return true;
+
+  // @ts-ignore
+  // Note: as of recently, this NO LONGER DETECTS FIREFOX ON iOS!
+  // This means FF/iOS will behave like on the desktop, and loose
+  // selection handlebars after the popup opens.
+  if (/iPad|iPhone/.test(userAgent) && !window.MSStream)
+    return true;
+
+  return false;
+}
\ No newline at end of file
diff --git a/packages/text-annotator-react/test/App.tsx b/packages/text-annotator-react/test/App.tsx
index d638e588..74849e2c 100644
--- a/packages/text-annotator-react/test/App.tsx
+++ b/packages/text-annotator-react/test/App.tsx
@@ -1,6 +1,6 @@
 import React, { FC, useCallback, useEffect } from 'react';
-import { AnnotationBody, Annotorious, useAnnotationStore, useAnnotator, useSelection } from '@annotorious/react';
-import { TextAnnotator, TextAnnotatorPopup, type TextAnnotationPopupContentProps } from '../src';
+import { AnnotationBody, Annotorious, useAnnotationStore, useAnnotator } from '@annotorious/react';
+import { TextAnnotationPopupContentProps, TextAnnotator, TextAnnotatorPopup } from '../src';
 import { W3CTextFormat, type TextAnnotation, type TextAnnotator as RecogitoTextAnnotator } from '@recogito/text-annotator';
 
 const TestPopup: FC<TextAnnotationPopupContentProps> = (props) => {
diff --git a/packages/text-annotator/src/SelectionHandler.ts b/packages/text-annotator/src/SelectionHandler.ts
index 164c4ed7..801e0f1c 100644
--- a/packages/text-annotator/src/SelectionHandler.ts
+++ b/packages/text-annotator/src/SelectionHandler.ts
@@ -10,6 +10,7 @@ import {
   debounce,
   splitAnnotatableRanges,
   rangeToSelector,
+  isMac,
   isWhitespaceOrEmpty,
   trimRangeToContainer,
   NOT_ANNOTATABLE_SELECTOR
@@ -19,10 +20,11 @@ const CLICK_TIMEOUT = 300;
 
 const ARROW_KEYS = ['up', 'down', 'left', 'right'];
 
+const SELECT_ALL = isMac ? '⌘+a' :  'ctrl+a';
+
 const SELECTION_KEYS = [
   ...ARROW_KEYS.map(key => `shift+${key}`),
-  'ctrl+a',
-  '⌘+a'
+  SELECT_ALL
 ];
 
 export const SelectionHandler = (
@@ -48,7 +50,11 @@ export const SelectionHandler = (
 
   let lastDownEvent: Selection['event'] | undefined;
 
+  let isContextMenuOpen = false;
+
   const onSelectStart = (evt: Event) => {
+    isContextMenuOpen = false;
+    
     if (isLeftClick === false)
       return;
 
@@ -59,7 +65,6 @@ export const SelectionHandler = (
      * Note that Chrome/iOS will sometimes return the root doc as target!
      */
     const annotatable = !(evt.target as Node).parentElement?.closest(NOT_ANNOTATABLE_SELECTOR);
-
     currentTarget = annotatable ? {
       annotation: uuidv4(),
       selector: [],
@@ -92,15 +97,11 @@ export const SelectionHandler = (
 
         // Chrome/iOS does not reliably fire the 'selectstart' event!
         onSelectStart(lastDownEvent || evt);
-
       } else if (sel.isCollapsed && timeDifference < CLICK_TIMEOUT) {
 
-        /*
-         Firefox doesn't fire the 'selectstart' when user clicks
-         over the text, which collapses the selection
-        */
+        // Firefox doesn't fire the 'selectstart' when user clicks
+        // over the text, which collapses the selection
         onSelectStart(lastDownEvent || evt);
-
       }
     }
 
@@ -142,21 +143,12 @@ export const SelectionHandler = (
       updated: new Date()
     };
 
+    /**
+     * During mouse selection on the desktop, annotation won't usually exist while the selection is being edited.
+     * But it will be typical during keyboard or mobile handlebars selection!
+     */
     if (store.getAnnotation(currentTarget.annotation)) {
       store.updateTarget(currentTarget, Origin.LOCAL);
-    } else {
-      // Proper lifecycle management: clear selection first...
-      selection.clear();
-
-      // ...then add annotation to store...
-      store.addAnnotation({
-        id: currentTarget.annotation,
-        bodies: [],
-        target: currentTarget
-      });
-
-      // ...then make the new annotation the current selection
-      selection.userSelect(currentTarget.annotation, lastDownEvent);
     }
   });
 
@@ -166,6 +158,8 @@ export const SelectionHandler = (
    * to the initial pointerdown event and remember the button
    */
   const onPointerDown = (evt: PointerEvent) => {
+    if (isContextMenuOpen) return;
+
     const annotatable = !(evt.target as Node).parentElement?.closest(NOT_ANNOTATABLE_SELECTOR);
     if (!annotatable) return;
 
@@ -177,7 +171,23 @@ export const SelectionHandler = (
     isLeftClick = lastDownEvent.button === 0;
   };
 
+  // Helper
+  const upsertCurrentTarget = () => {
+    const exists = store.getAnnotation(currentTarget.annotation);
+    if (exists) {
+      store.updateTarget(currentTarget);
+    } else {
+      store.addAnnotation({
+        id: currentTarget.annotation,
+        bodies: [],
+        target: currentTarget
+      });
+    }
+  }
+
   const onPointerUp = (evt: PointerEvent) => {
+    if (isContextMenuOpen) return;
+
     const annotatable = !(evt.target as Node).parentElement?.closest(NOT_ANNOTATABLE_SELECTOR);
     if (!annotatable || !isLeftClick) return;
 
@@ -193,8 +203,9 @@ export const SelectionHandler = (
       if (hovered) {
         const { selected } = selection;
 
-        if (selected.length !== 1 || selected[0].id !== hovered.id)
+        if (selected.length !== 1 || selected[0].id !== hovered.id) {
           selection.userSelect(hovered.id, evt);
+        }
       } else if (!selection.isEmpty()) {
         selection.clear();
       }
@@ -212,23 +223,87 @@ export const SelectionHandler = (
      * @see https://github.com/recogito/text-annotator-js/issues/136
      */
     setTimeout(() => {
-      const sel = document.getSelection()
+      const sel = document.getSelection();
 
       // Just a click, not a selection
       if (sel?.isCollapsed && timeDifference < CLICK_TIMEOUT) {
         currentTarget = undefined;
         clickSelect();
-      } else if (currentTarget && store.getAnnotation(currentTarget.annotation)) {
-        selection.userSelect(currentTarget.annotation, evt);
+      } else if (currentTarget && currentTarget.selector.length > 0) {
+        selection.clear();
+        upsertCurrentTarget();
+        selection.userSelect(currentTarget.annotation, clonePointerEvent(evt));
       }
     });
   }
 
-  hotkeys(SELECTION_KEYS.join(','), { element: container, keydown: true, keyup: false }, (evt) => {
+  const onContextMenu = (evt: PointerEvent) => {
+    isContextMenuOpen = true;
+
+    const sel = document.getSelection();
+
+    if (sel?.isCollapsed) return;
+
+    // When selecting the initial word, Chrome Android fires `contextmenu` 
+    // before selectionChanged.
+    if (!currentTarget || currentTarget.selector.length === 0) {
+      onSelectionChange(evt);
+    }
+    
+    upsertCurrentTarget();
+
+    selection.userSelect(currentTarget.annotation, clonePointerEvent(evt));
+  }
+
+  const onKeyup = (evt: KeyboardEvent) => {
+    if (evt.key === 'Shift' && currentTarget) {
+      const sel = document.getSelection();
+
+      if (!sel.isCollapsed) {
+        selection.clear();
+        upsertCurrentTarget();
+        selection.userSelect(currentTarget.annotation, cloneKeyboardEvent(evt));
+      }
+    }
+  }
+
+  const onSelectAll = (evt: KeyboardEvent) => {
+
+    const onSelected = () => setTimeout(() => {
+      if (currentTarget?.selector.length > 0) {
+        selection.clear();
+
+        store.addAnnotation({
+          id: currentTarget.annotation,
+          bodies: [],
+          target: currentTarget
+        });
+
+        selection.userSelect(currentTarget.annotation, cloneKeyboardEvent(evt));
+      }
+      
+      document.removeEventListener('selectionchange', onSelected);
+
+      // Sigh... this needs a delay to work. But doesn't seem reliable.
+    }, 100);
+
+    // Listen to the change event that follows
+    document.addEventListener('selectionchange', onSelected);
+    
+    // Start selection!
+    onSelectStart(evt);
+  }
+
+  hotkeys(SELECTION_KEYS.join(','), { element: container, keydown: true, keyup: false }, evt => {
     if (!evt.repeat)
       lastDownEvent = cloneKeyboardEvent(evt);
   });
 
+  hotkeys(SELECT_ALL, { keydown: true, keyup: false}, evt => {
+    lastDownEvent = cloneKeyboardEvent(evt);
+    onSelectAll(evt);
+  });
+
   /**
    * Free caret movement through the text resets the annotation selection.
    *
@@ -253,8 +328,10 @@ export const SelectionHandler = (
 
   container.addEventListener('pointerdown', onPointerDown);
   document.addEventListener('pointerup', onPointerUp);
+  document.addEventListener('contextmenu', onContextMenu);
 
   if (annotatingEnabled) {
+    container.addEventListener('keyup', onKeyup);
     container.addEventListener('selectstart', onSelectStart);
     document.addEventListener('selectionchange', onSelectionChange);
   }
@@ -262,7 +339,9 @@ export const SelectionHandler = (
   const destroy = () => {
     container.removeEventListener('pointerdown', onPointerDown);
     document.removeEventListener('pointerup', onPointerUp);
+    document.removeEventListener('contextmenu', onContextMenu);
 
+    container.removeEventListener('keyup', onKeyup);
     container.removeEventListener('selectstart', onSelectStart);
     document.removeEventListener('selectionchange', onSelectionChange);
 
diff --git a/packages/text-annotator/src/utils/device.ts b/packages/text-annotator/src/utils/device.ts
new file mode 100644
index 00000000..a935d82a
--- /dev/null
+++ b/packages/text-annotator/src/utils/device.ts
@@ -0,0 +1,2 @@
+// @ts-ignore
+export const isMac = /mac/i.test(navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform);
diff --git a/packages/text-annotator/src/utils/index.ts b/packages/text-annotator/src/utils/index.ts
index f1284390..fe84a63b 100644
--- a/packages/text-annotator/src/utils/index.ts
+++ b/packages/text-annotator/src/utils/index.ts
@@ -1,4 +1,5 @@
 export * from './cancelSingleClickEvents';
+export * from './device';
 export * from './programmaticallyFocusable';
 export * from './debounce';
 export * from './getAnnotatableFragment';