RTELink Popover
- Stable
- WCAG 2.2 evidence
- RTL
The link editor of a rich-text editor. Mode=Edit is a small form: the address field (the
library Input, with the link icon), "Open in new tab" (CheckboxField), Cancel and Apply (Button). Mode=Preview
shows the address as a link that opens in a new tab, with Edit and Remove (IconButton). Built on a non-modal React Aria Popover holding a role="dialog" panel named "Edit link" / "Link". Open it from the
link tool inside a React Aria DialogTrigger (the tool gets aria-expanded), or anchor it with triggerRef and
isOpen. In edit mode focus moves into the address field when it opens (WCAG 2.4.3); the address is checked when
Apply or Enter is pressed and an invalid one shows an inline error with an icon, linked to the field, and keeps focus
there (3.3.1, 3.3.3, 4.1.2, 1.4.1). Escape, Cancel, Apply and Remove close it and focus returns to the trigger or the
editor (2.1.1); nothing is trapped and a click outside closes it (2.1.2). It flips and stays inside the viewport.
Preview mode does not take focus, so typing continues in the editor; keyboard users reach the same actions through the
link tool (edit mode). The library ships no editor engine: apply and remove the link in onSubmit / onRemove. Consumer duties: texts in
your locale (labels), and opening edit mode from the toolbar.
import { RTELinkPopover } from "@nexera-ui/react";- 5
- examples
- 30
- props
- 5
- live controls
- 1
- platform
- 11
- WCAG criteria
- 1
- block uses it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { RTELinkPopover } from "@nexera-ui/react";
<RTELinkPopover />Examples 4
The same examples as Storybook, rendered live. Open Code to copy one.
With text field
hasTextField: a "Text to display" field under the address, for links inserted without selected text.
import { RTELinkPopover } from "@nexera-ui/react";
<RTELinkPopover hasTextField text="Benefits guide" />;
Preview
Figma Mode=Preview, anchored to a link in the text (triggerRef): the address opens in a new tab; Edit switches to the form, Remove removes the link. It does not take focus, so typing continues in the editor.
import { useRef, useState } from "react";
import { RTELinkPopover } from "@nexera-ui/react";
export function Preview() {
const anchor = useRef<HTMLAnchorElement>(null);
const [open, setOpen] = useState(true);
return (
<div className="max-w-prose">
<p className="m-0 text-body-large text-primary">
Read the{" "}
<a ref={anchor} href="https://intranet.company.com/benefits" className="text-brand-text">
benefits guide
</a>{" "}
before you pick a plan.
</p>
<RTELinkPopover
triggerRef={anchor}
isOpen={open}
onOpenChange={setOpen}
defaultMode="preview"
href="https://intranet.company.com/benefits"
openInNewTab
/>
</div>
);
}
Right to left
Right-to-left with translated texts, and a long address that wraps instead of being cut.
import { useRef } from "react";
import { RTELinkPopover } from "@nexera-ui/react";
export function RightToLeft() {
const anchor = useRef<HTMLButtonElement>(null);
return (
<div>
<button ref={anchor} type="button" className="text-body-default">
الرابط
</button>
<RTELinkPopover
triggerRef={anchor}
defaultOpen
defaultMode="preview"
href="https://intranet.company.com/benefits/health-plans/standard-and-plus/comparison-2026"
labels={{
previewDialog: "رابط",
edit: "تعديل الرابط",
remove: "إزالة الرابط",
opensInNewTab: "(يفتح في علامة تبويب جديدة)",
editDialog: "تعديل الرابط",
url: "عنوان الرابط",
urlPlaceholder: "الصق رابطًا أو اكتبه",
openInNewTab: "فتح في علامة تبويب جديدة",
cancel: "إلغاء",
apply: "تطبيق",
}}
/>
</div>
);
}
In editor
A realistic composed editor with a plain contenteditable element (Ctrl+K or the Link tool opens the popover over the selected text).
export function InEditor() {
return <RichTextEditorDemo />;
}
Props 30
Press "Try it" on a card to load that prop into the playground.
30 props shown
modeNexera"edit" | "preview"Figma Mode: edit shows the address form, preview shows the link with Edit and Remove buttons (for example when
the caret is inside a link). Controlled; use with onModeChange. Edit in preview switches to edit.
defaultModeNexera"edit" | "preview"Mode on first render (uncontrolled).
onModeChangeNexera(mode: RTELinkPopoverMode) => voidCalled when the mode changes (the preview's Edit button).
hrefNexerastringThe current link address: shown in preview, and the starting value of the edit field (empty for a new link).
textNexerastringThe current link text: the starting value of the text field (with hasTextField).
openInNewTabNexerabooleanWhether the current link opens in a new tab: the starting value of the checkbox.
hasTextFieldNexerabooleanAdds a "Text to display" field under the address. Use it when the link has no selected text to wrap.
onSubmitNexera(link: RTELinkValue) => voidCalled with the validated link when Apply is pressed (or Enter in a field); the popover then closes and focus returns to the trigger or the editor. Apply the link in your editor here.
onRemoveNexera() => voidCalled when the preview's Remove button is pressed; the popover then closes. Remove the link in your editor here.
validateNexera(href: string) => string | nullExtra check of the address after the built-in one (which accepts web, mail and phone links and relative paths). Return an error message to block the submission; it is shown under the field with an icon.
labelsNexeraPartial<RTELinkPopoverLabels>The texts the popover renders, merged over the English defaults. Translate them for your locale.
triggerRefNexeraRefObject<Element | null>The element the popover is anchored to and gives focus back to, when it is not the trigger of a surrounding
DialogTrigger: the link tool, or the link element in the editor for the preview.
placementNexera"bottom" | "bottom left" | "bottom right" | "bottom start" | "bottom end" | "top" | "top left" | "top right" | "top start" | "top end" | "left" | "left top" | "left bottom" | "start" | "start top" | "start bottom" | "right" | "right top" | "right bottom" | "end" | "end top" | "end bottom"Side of the anchor the popover opens on; it flips to stay inside the viewport.
classNameNexerastringExtra classes for the surface, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style for the surface.
isOpenReact AriabooleanWhether the overlay is open by default (controlled).
containerPaddingReact ArianumberThe placement padding that should be applied between the element and its surrounding container.
offsetReact ArianumberThe additional offset applied along the main axis between the element and its anchor element.
crossOffsetReact ArianumberThe additional offset applied along the cross axis between the element and its anchor element.
shouldFlipReact AriabooleanWhether the element should flip its orientation (e.g. top to bottom or left to right) when there is insufficient room for it to render completely.
boundaryElementReact AriaElementElement that that serves as the positioning boundary.
scrollRefReact AriaRefObject<Element | null>A ref for the scrollable region within the overlay.
shouldUpdatePositionReact AriabooleanWhether the overlay should update its position automatically.
maxHeightReact ArianumberThe maxHeight specified for the overlay element. By default, it will take all space up to the current viewport height.
arrowBoundaryOffsetReact ArianumberThe minimum distance the arrow's edge should be from the edge of the overlay element.
getTargetRectReact Aria(target: Element) => DOMRect | nullOverrides the target element's bounding rectangle. Useful for positioning relative to a specific point such as the mouse cursor (e.g. context menus) or text selection. @param target - The target element.
isKeyboardDismissDisabledReact AriabooleanWhether pressing the escape key to close the popover should be disabled. Most popovers should not use this option. When set to true, an alternative way to close the popover with a keyboard must be provided.
shouldCloseOnInteractOutsideReact Aria(element: Element) => booleanWhen user interacts with the argument element outside of the popover ref, return true if onClose should be called. This gives you a chance to filter out interaction with elements that should not dismiss the popover. By default, onClose will always be called on interaction outside the popover ref.
defaultOpenReact AriabooleanWhether the overlay is open by default (uncontrolled).
onOpenChangeReact Aria(isOpen: boolean) => voidHandler that is called when the overlay's open state changes.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
9 direct · 2 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.10ReflowLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.1.2No Keyboard TrapLevel A · tested directly
- 2.4.3Focus OrderLevel A · tested directly
- 2.4.4Link Purpose (In Context)Level A · tested directly
- 3.3.1Error IdentificationLevel A · tested directly
- 3.3.3Error SuggestionLevel AA · tested directly
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-hovered
<RTELinkPopover className="data-hovered:opacity-90 shadow-sm" />Used in blocks
Related components
- CalendarDayOne day of a {@link CalendarMonth } grid, built on React Aria `CalendarCell`: a `gridcell` whose button is named by the full, localised date ("Wednesday, October 14, 2026"), with `aria-selected`, `aria-disabled` and the "today" and range descriptions read by screen readers (WCAG 1.3.1, 4.1.2).
- CalendarMonthA month calendar for picking a date or a date range: header with previous / next buttons and the localised month name, weekday row, and six weeks of `CalendarDay`s.
- CheckboxThe bare 18 px checkbox: one independent yes/no choice applied on submit, or a row selector in a table, list or tree.
- CheckboxCardA large checkbox option with an icon, a title and a description, for a few options that need explanation (notification channels, benefits).
- CheckboxFieldA checkbox with a label and optional helper text: the box (the same element as `Checkbox`) followed by the label and description, applied on submit.
- ColorInputA hex colour field with a swatch and a picker popover.
- ColorPickerPicks a colour.
- ColorTokenCardDocumentation card for one colour token: a 96 px colour sample with an "Aa" text sample, then the name, token path, hex value and contrast note as text.