Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
98cb3d8
chore: reference s2 ai components
snowystinger Aug 31, 2026
86a1d51
lint: fix docs type check
reidbarber Aug 31, 2026
32331ce
rename to ai-components.mdx (matches guide naming)
reidbarber Aug 31, 2026
eaf04de
add page description
reidbarber Aug 31, 2026
62f1500
use progressive sections
reidbarber Aug 31, 2026
c679acb
group consecutive tool/status messages (Thinking, Loading tool, Searc…
reidbarber Aug 31, 2026
c0aaca3
add installation section, alpha badge, API section, PropTables, and r…
reidbarber Aug 31, 2026
7d5c042
typescript fixes
reidbarber Aug 31, 2026
8064621
fix lint
reidbarber Aug 31, 2026
3e48be1
add missing JSDoc descriptions
reidbarber Aug 31, 2026
487c037
small styling fixes, make full example stream
LFDanLu Aug 31, 2026
ea0f4eb
simplify and clean up complete docs example
LFDanLu Aug 31, 2026
5171770
add illustration for AI components
LFDanLu Aug 31, 2026
460ebb1
add missing api sections, update chat example with prose, add token t…
LFDanLu Sep 1, 2026
3d5af86
move complete example to top, fix token styling for suggestions example
LFDanLu Sep 1, 2026
f8fe7f3
fix props
snowystinger Sep 1, 2026
57f8d48
Merge branch 'main' into new-s2-ai-component-reference
snowystinger Sep 1, 2026
bf5be04
Merge branch 'main' of github.com:adobe/react-spectrum into new-s2-ai…
devongovett Sep 1, 2026
5d5def8
minor example updates
devongovett Sep 1, 2026
52d6430
use transparent color so it works over any background
devongovett Sep 1, 2026
46d5097
fix ts
snowystinger Sep 1, 2026
c98ddbe
update API section for consistent API anatomy
LFDanLu Sep 1, 2026
e817e9f
Merge branch 'new-s2-ai-component-reference' of github.com:adobe/reac…
LFDanLu Sep 1, 2026
982c889
fix alpha badge
snowystinger Sep 1, 2026
33f6aa6
Add Alert to API
snowystinger Sep 1, 2026
795642b
make promptfield smaller in example with control
snowystinger Sep 1, 2026
394666f
fix Chat example height and background color
snowystinger Sep 1, 2026
a3d7d55
add icon for summarize menu item
snowystinger Sep 1, 2026
290620d
updates
devongovett Sep 1, 2026
788aedd
more missing descriptions
devongovett Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/@react-spectrum/ai/src/AIButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ const bg = css(`
}
`);

/**
* An AIButton triggers an AI-powered action with a customizable branded appearance.
*/
export function AIButton({size = 'M', brandColor, children, ...otherProps}: AIButtonProps) {
let ref = useRef<HTMLButtonElement | null>(null);
return (
Expand Down
3 changes: 3 additions & 0 deletions packages/@react-spectrum/ai/src/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ const text = style({
minWidth: 0
});

/**
* An Alert shows an error message within a Chat thread or PromptField.
*/
export const Alert = forwardRef(function Alert(props: AlertProps, ref: DOMRef<HTMLDivElement>) {
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/ai');
let {children, variant = 'neutral', onDismiss, styles} = props;
Expand Down
10 changes: 10 additions & 0 deletions packages/@react-spectrum/ai/src/AttachmentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ const flexRow = {

const tagListStyles = style({
...flexRow,
flexGrow: 1,
gap: 8,
overflowX: 'auto',
overflowY: 'clip',
Expand Down Expand Up @@ -439,6 +440,9 @@ function CarouselNavButton({side, ...otherProps}: ButtonProps & {side: 'start' |
// oxlint-enable react/react-compiler
}

/**
* An AttachmentList displays removable file attachments with previews and upload states.
*/
export const AttachmentList = (forwardRef as forwardRefType)(function AttachmentList<T>(
props: AttachmentListProps<T>,
ref: DOMRef<HTMLDivElement>
Expand Down Expand Up @@ -602,6 +606,9 @@ function AttachmentCard({
);
}

/**
* Attachment displays an individual file attachment within a PromptFieldAttachmentList.
*/
export const Attachment = forwardRef(function Attachment(
props: AttachmentProps,
ref: DOMRef<HTMLDivElement>
Expand Down Expand Up @@ -666,6 +673,9 @@ export interface AttachmentPreviewProps extends ImageProps {
mimeType: string;
}

/**
* AttachmentPreview renders a preview of a file attachment.
*/
export function AttachmentPreview(props: AttachmentPreviewProps) {
let {mimeType, ...otherProps} = props;
let {isInvalid, uploadProgress, size} = useContext(AttachmentPreviewContext)!;
Expand Down
15 changes: 15 additions & 0 deletions packages/@react-spectrum/ai/src/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ export interface ChatProps {
children?: ReactNode;
}

/**
* A Chat displays an accessible, streaming conversation between a user and an AI.
*/
export const Chat = /*#__PURE__*/ (forwardRef as forwardRefType)(function Chat(
props: ChatProps,
ref: DOMRef<HTMLDivElement>
Expand Down Expand Up @@ -249,6 +252,9 @@ export interface ThreadProps<T extends object> extends Pick<
scrollEndThreshold?: number;
}

/**
* A Thread shows a conversation within a Chat.
*/
export function Thread<T extends object>(props: ThreadProps<T>) {
let {
items,
Expand Down Expand Up @@ -324,6 +330,9 @@ export interface ThreadScrollButtonProps {

// TODO: wrapper so we can do the "if isNearBottom then hide" logic, could do this via inline styles perhaps
// and ditch the wrapper?
/**
* A ThreadScrollButton displays a button to scroll to the bottom of a Chat thread.
*/
export function ThreadScrollButton({children}: ThreadScrollButtonProps) {
let {isNearBottom, scrollToBottom, ...buttonProps} = useContext(ThreadScrollButtonContext);
let ref = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -378,6 +387,9 @@ export interface ThreadItemProps extends Pick<
shouldAnnounceOnMount?: boolean;
}

/**
* A ThreadItem displays an individual chat message.
*/
export function ThreadItem(props: ThreadItemProps) {
let {
styles,
Expand Down Expand Up @@ -429,6 +441,9 @@ export interface ThreadLoadMoreItemProps extends GridListLoadMoreItemProps {}

// TODO: Reuse GridListLoadMoreItem instead when Thread component moves into RAC.
// Re-implementing here so we can avoid passing 'direction' to the LoadMore item
/**
* A ThreadLoadMoreItem loads more chat messages when it is scrolled into the viewport.
*/
export const ThreadLoadMoreItem = createLeafComponent(
LoaderNode,
function GridListLoadingIndicator(
Expand Down
38 changes: 38 additions & 0 deletions packages/@react-spectrum/ai/src/PromptField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export interface PromptFieldProps {
onRemoveAttachments?: (attachments: PromptFieldAttachment[]) => void;
onAITermsPress?: () => void;
styles?: StyleString;
/** @default 'balanced' */
variant?: 'balanced' | 'prominent' | 'subtle';
brandColor?: string;
/**
Expand Down Expand Up @@ -256,6 +257,10 @@ function matchMimeType(mimeType: string, acceptedMimeTypes: string[]): boolean {
});
}

/**
* A PromptField allows users to compose and submit prompts containing text, tokens, and
* attachments.
*/
export const PromptField = forwardRef(function PromptField(
props: PromptFieldProps,
ref: FocusableRef<HTMLDivElement>
Expand Down Expand Up @@ -389,6 +394,9 @@ export interface PromptFieldAttachmentListProps extends AttachmentListProps<Prom
children?: (attachment: PromptFieldAttachment) => React.ReactNode;
}

/**
* PromptFieldAttachmentList displays a list of file attachments within a PromptField.
*/
export function PromptFieldAttachmentList(props: PromptFieldAttachmentListProps) {
let {children} = props;
let {attachments, setAttachments, onRemoveAttachments, inputRef} = useContext(PromptFieldContext);
Expand Down Expand Up @@ -436,6 +444,10 @@ export interface PromptTokenFieldProps {
menuWidth?: number;
}

/**
* PromptTokenField renders an editable text input for a prompt, and supports inserting inline
* object references as tokens via autocomplete.
*/
export function PromptTokenField(props: PromptTokenFieldProps) {
let {
completionTrigger,
Expand Down Expand Up @@ -792,6 +804,9 @@ export interface PromptTokenProps extends Omit<TokenProps, 'children' | 'render'
children: React.ReactNode;
}

/**
* A PromptToken displays a non-editable inline object reference within a PromptTokenField.
*/
export function PromptToken(props: PromptTokenProps) {
let {size} = useContext(PromptFieldContext)!;
return (
Expand Down Expand Up @@ -861,6 +876,9 @@ export interface PromptFieldToolbarProps {
children: React.ReactNode;
}

/**
* PromptFieldToolbar contains action buttons related to the PromptField.
*/
export function PromptFieldToolbar(props: PromptFieldToolbarProps) {
let {children} = props;
return (
Expand All @@ -878,6 +896,7 @@ export function PromptFieldToolbar(props: PromptFieldToolbarProps) {

export interface PromptFieldSubmitButtonProps {}

/** PromptFieldSubmitButton submits the PromptField. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function PromptFieldSubmitButton(props: PromptFieldSubmitButtonProps) {
let {prompt, isGenerating, onSubmit, onStop} = useContext(PromptFieldContext);
Expand Down Expand Up @@ -906,6 +925,9 @@ export interface PromptFieldVoiceButtonProps {
onToggle?: (isListening: boolean) => void;
}

/**
* PromptFieldVoiceButton triggers voice input for the PromptField.
*/
export function PromptFieldVoiceButton(props: PromptFieldVoiceButtonProps) {
let {lang: langProp, isDisabled: isDisabledProp, onError, onToggle} = props;
let {locale} = useLocale();
Expand Down Expand Up @@ -1008,6 +1030,9 @@ export interface InsertMenuItemProps extends Pick<MenuTriggerProps, 'onOpenChang
children: React.ReactNode;
}

/**
* InsertMenuButton renders an ActionButton with a plus icon that opens a menu.
*/
export function InsertMenuButton(props: InsertMenuItemProps) {
let {children, onOpenChange} = props;
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/ai');
Expand Down Expand Up @@ -1038,6 +1063,9 @@ export interface AttachFileMenuItemProps extends Omit<
| 'target'
> {}

/**
* AttachFileMenuItem triggers a system file dialog to attach files within an InsertMenuButton.
*/
export function AttachFileMenuItem(props: AttachFileMenuItemProps) {
let {onAction, ...otherProps} = props;
let {acceptedAttachmentTypes, setAttachments, onAddAttachments} = useContext(PromptFieldContext);
Expand Down Expand Up @@ -1144,6 +1172,10 @@ export interface InsertTokenMenuItemProps extends Omit<
token: TokenSegment<PromptFieldTokenValue>;
}

/**
* InsertTokenMenuItem inserts a token (i.e. object reference) into the PromptField within an
* InsertMenuButton.
*/
export function InsertTokenMenuItem(props: InsertTokenMenuItemProps) {
let insert = useInsertPromptSegment([props.token]);

Expand Down Expand Up @@ -1175,6 +1207,9 @@ export interface InsertTextMenuItemProps extends Omit<
text: string;
}

/**
* InsertTextMenuItem inserts plain text into the PromptField from within an InsertMenuButton.
*/
export function InsertTextMenuItem(props: InsertTextMenuItemProps) {
let insert = useInsertPromptSegment([{type: 'text', text: props.text}]);

Expand Down Expand Up @@ -1205,6 +1240,9 @@ export interface CommandMenuItemProps extends Omit<
// specifically for menu items that only trigger a callback in the autocomplete menu
// since they dont end up inserting a token or text, we need to clear the partial text that the user used
// to filter the menu
/**
* CommandMenuItem performs an immediate action from within an InsertMenuButton.
*/
export function CommandMenuItem(props: CommandMenuItemProps) {
let insert = useInsertPromptSegment([]);
return (
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/ai/src/ResponseStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ const executionTraceDetailStyle = style({
// focus ring needs to be here instead of child detail div since the fade fades the focus ring
...focusRing(),
outlineOffset: -2,
backgroundColor: 'layer-1',
backgroundColor: 'transparent-overlay-50',
borderRadius: 'lg',
font: 'body-2xs',
color: 'gray-600'
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/ai/src/UserMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const bubble = style({
default: 16,
':has(img)': 8
},
backgroundColor: 'gray-50',
backgroundColor: 'transparent-overlay-50',
color: 'neutral',
borderRadius: 'lg',
font: 'body',
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/ai/src/loader/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ export function PixelLoader(props: PixelLoaderProps) {
}
return matrix;
}, [cells]);
const isHighDPI = window.devicePixelRatio >= 2;
const isHighDPI = (typeof document !== 'undefined' && window.devicePixelRatio >= 2) ?? false;

return (
<div
Expand Down
96 changes: 50 additions & 46 deletions packages/@react-spectrum/ai/src/speech-recognition.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,57 +18,61 @@
// lib.dom and are not redeclared.
// Spec: https://wicg.github.io/speech-api/

interface SpeechRecognitionEvent extends Event {
readonly resultIndex: number;
readonly results: SpeechRecognitionResultList;
}
export {};

interface SpeechRecognitionErrorEvent extends Event {
readonly error: SpeechRecognitionErrorCode;
readonly message: string;
}
declare global {
interface SpeechRecognitionEvent extends Event {
readonly resultIndex: number;
readonly results: SpeechRecognitionResultList;
}

interface SpeechRecognition extends EventTarget {
lang: string;
continuous: boolean;
interimResults: boolean;
maxAlternatives: number;
start(): void;
stop(): void;
abort(): void;
onaudiostart: ((this: SpeechRecognition, ev: Event) => void) | null;
onsoundstart: ((this: SpeechRecognition, ev: Event) => void) | null;
onspeechstart: ((this: SpeechRecognition, ev: Event) => void) | null;
onspeechend: ((this: SpeechRecognition, ev: Event) => void) | null;
onsoundend: ((this: SpeechRecognition, ev: Event) => void) | null;
onaudioend: ((this: SpeechRecognition, ev: Event) => void) | null;
onresult: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;
onnomatch: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;
onerror: ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void) | null;
onstart: ((this: SpeechRecognition, ev: Event) => void) | null;
onend: ((this: SpeechRecognition, ev: Event) => void) | null;
}
interface SpeechRecognitionErrorEvent extends Event {
readonly error: SpeechRecognitionErrorCode;
readonly message: string;
}

interface SpeechRecognitionConstructor {
new (): SpeechRecognition;
}
interface SpeechRecognition extends EventTarget {
lang: string;
continuous: boolean;
interimResults: boolean;
maxAlternatives: number;
start(): void;
stop(): void;
abort(): void;
onaudiostart: ((this: SpeechRecognition, ev: Event) => void) | null;
onsoundstart: ((this: SpeechRecognition, ev: Event) => void) | null;
onspeechstart: ((this: SpeechRecognition, ev: Event) => void) | null;
onspeechend: ((this: SpeechRecognition, ev: Event) => void) | null;
onsoundend: ((this: SpeechRecognition, ev: Event) => void) | null;
onaudioend: ((this: SpeechRecognition, ev: Event) => void) | null;
onresult: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;
onnomatch: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;
onerror: ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void) | null;
onstart: ((this: SpeechRecognition, ev: Event) => void) | null;
onend: ((this: SpeechRecognition, ev: Event) => void) | null;
}

interface Window {
SpeechRecognition?: SpeechRecognitionConstructor;
webkitSpeechRecognition?: SpeechRecognitionConstructor;
}
interface SpeechRecognitionConstructor {
new (): SpeechRecognition;
}

// User-Agent Client Hints — present in Chromium, absent in Safari/Firefox.
interface NavigatorUABrandVersion {
readonly brand: string;
readonly version: string;
}
interface Window {
SpeechRecognition?: SpeechRecognitionConstructor;
webkitSpeechRecognition?: SpeechRecognitionConstructor;
}

interface NavigatorUAData {
readonly brands: ReadonlyArray<NavigatorUABrandVersion>;
readonly platform?: string;
}
// User-Agent Client Hints — present in Chromium, absent in Safari/Firefox.
interface NavigatorUABrandVersion {
readonly brand: string;
readonly version: string;
}

interface NavigatorUAData {
readonly brands: ReadonlyArray<NavigatorUABrandVersion>;
readonly platform?: string;
}

interface Navigator {
readonly userAgentData?: NavigatorUAData;
interface Navigator {
readonly userAgentData?: NavigatorUAData;
}
}
2 changes: 2 additions & 0 deletions packages/@react-spectrum/ai/src/useVoiceInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/

/// <reference path="./speech-recognition.d.ts" />

import {
Dispatch,
RefObject,
Expand Down
Loading