# Sistema de diseño Microsoft Fluent 2

## Microsoft Fluent 2 Vista previa de la interfaz del sistema de diseño

Alterna entre Resumen y Personas, cambia el tema web y prueba adiciones no válidas, duplicadas y correctas en local. No se envía ninguna invitación.

## Microsoft Fluent 2 Referencia de diseño del sistema de diseño

Referencias web de Fluent UI React v9. Los valores numéricos globales proceden de las fuentes de tokens enlazadas de React. Los valores de color de abajo corresponden al mapeo de alias claro con brandWeb, no a los comentarios hexadecimales obsoletos del ejemplo en alias/lightColor.ts. Los colores oscuros y los temas de Teams tienen mapeos independientes. Actualmente, la tabla tipográfica del sitio indica Subtitle1 a 20/26; la fuente React fonts.ts usa 20/28, valor que sigue esta implementación. Fuentes consultadas el 3 de octubre de 2026; comprueba la versión indicada en tu archivo de bloqueo.

### Roles neutros del tema claro

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| colorNeutralForeground1 | #242424 | Texto principal del documento. |
| colorNeutralForeground2 | #424242 | Etiquetas secundarias. |
| colorNeutralForeground3 | #616161 | Metadatos; comprueba también el contraste final. |
| colorNeutralBackground1 | #ffffff | Superficie principal. |
| colorNeutralBackground2 | #fafafa | Superficie de apoyo discreta. |
| colorNeutralBackground3 | #f5f5f5 | Lienzo del espacio de trabajo. |
| colorNeutralStroke1 | #d1d1d1 | Borde de componente habitual. |
| colorNeutralStroke2 | #e0e0e0 | Separador sutil. |
| colorNeutralStrokeAccessible | #616161 | Borde de control de mayor contraste. |
| colorNeutralForegroundOnBrand | #ffffff | Texto sobre los fondos de marca admitidos. |
| colorStrokeFocus1 / colorStrokeFocus2 | #ffffff / #000000 | Dos colores de foco; conserva el contraste en el borde del componente. |
### Roles de marca web, resueltos a partir de brandWeb

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| colorBrandBackground | #0f6cbd · brandWeb[80] | Acción principal en webLightTheme. |
| colorBrandBackgroundHover | #115ea3 · brandWeb[70] | Variante al pasar el cursor de esa acción. |
| colorBrandBackgroundPressed | #0c3b5e · brandWeb[40] | Variante al presionar; no es un estado genérico de selección. |
| colorBrandBackground2 | #ebf3fc · brandWeb[160] | Fondo sutil de marca. |
| colorBrandForegroundLink | #115ea3 · brandWeb[70] | Color de primer plano de los enlaces en el tema web claro. |
### Espaciado horizontal y vertical

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| spacingHorizontalNone / spacingVerticalNone | 0 | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalXXS / spacingVerticalXXS | 2px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalXS / spacingVerticalXS | 4px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalSNudge / spacingVerticalSNudge | 6px | Elige el eje según la dirección del diseño; ajuste de alineación de iconos. |
| spacingHorizontalS / spacingVerticalS | 8px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalMNudge / spacingVerticalMNudge | 10px | Elige el eje según la dirección del diseño; ajuste de alineación de iconos. |
| spacingHorizontalM / spacingVerticalM | 12px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalL / spacingVerticalL | 16px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalXL / spacingVerticalXL | 20px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalXXL / spacingVerticalXXL | 24px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
| spacingHorizontalXXXL / spacingVerticalXXXL | 32px | Elige el eje según la dirección del diseño; paso de espaciado compartido. |
### React combinaciones tipográficas web

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| Caption2 · fontSizeBase100 | 10px / 14px / 400 | Tamaño / altura de línea / grosor. Combínalo con lineHeightBase100. |
| Caption1 · fontSizeBase200 | 12px / 16px / 400 | Tamaño / altura de línea / grosor. Combínalo con lineHeightBase200. |
| Body1 · fontSizeBase300 | 14px / 20px / 400 | Tamaño / altura de línea / grosor. Combínalo con lineHeightBase300. |
| Body2 / Subtitle2 · fontSizeBase400 | 16px / 22px / 400 / 600 | Tamaño / altura de línea / grosor. Combínalo con lineHeightBase400. |
| Subtitle1 · fontSizeBase500 | 20px / 28px / 600 | Tamaño / altura de línea / grosor. Combínalo con lineHeightBase500. |
| Title3 · fontSizeBase600 | 24px / 32px / 600 | Tamaño / altura de línea / grosor. Combínalo con lineHeightBase600. |
| Title2 · fontSizeHero700 | 28px / 36px / 600 | Tamaño / altura de línea / grosor. Combínalo con lineHeightHero700. |
| Title1 · fontSizeHero800 | 32px / 40px / 600 | Tamaño / altura de línea / grosor. Combínalo con lineHeightHero800. |
| LargeTitle · fontSizeHero900 | 40px / 52px / 600 | Tamaño / altura de línea / grosor. Combínalo con lineHeightHero900. |
| Display · fontSizeHero1000 | 68px / 92px / 600 | Tamaño / altura de línea / grosor. Combínalo con lineHeightHero1000. |
### Familia y grosor de fuente

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| fontFamilyBase | 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif | Pila web predeterminada del proveedor; no etiquetes la fuente del sitio que la rodea como Fluent. |
| fontFamilyMonospace | Consolas, 'Courier New', Courier, monospace | Código e identificadores técnicos. |
| fontWeightRegular / Medium / Semibold / Bold | 400 / 500 / 600 / 700 | Usa el grosor del rol junto con su escala tipográfica correspondiente. |
### Tokens de forma

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| borderRadiusNone | 0 | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadiusSmall | 2px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadiusMedium | 4px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadiusLarge | 6px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadiusXLarge | 8px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadius2XLarge | 12px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadius3XLarge | 16px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadius4XLarge | 24px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadius5XLarge | 32px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadius6XLarge | 40px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
| borderRadiusCircular | 10000px | Usa primero el valor predeterminado del componente; este es un token disponible, no un requisito para todas las superficies. |
### Escala de duración del movimiento

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| durationUltraFast | 50ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
| durationFaster | 100ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
| durationFast | 150ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
| durationNormal | 200ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
| durationGentle | 250ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
| durationSlow | 300ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
| durationSlower | 400ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
| durationUltraSlow | 500ms | Token disponible en tiempo de ejecución; el ejemplo usa Fast al pasar el cursor y elimina las transiciones cuando se activa la preferencia de movimiento reducido. |
### Los cambios de tema son semánticos, no una inversión

| Nombre / Regla | Valor / Configuración | Uso |
| --- | --- | --- |
| colorNeutralBackground1 | #292929 | Superficie principal oscura en lugar de la clara #ffffff. |
| colorNeutralForeground1 | #ffffff | Texto principal oscuro en lugar del claro #242424. |
| colorNeutralForeground2 | #d6d6d6 | Texto secundario oscuro. |
| colorBrandBackground | brandWeb[70] = #115ea3 | Relleno principal oscuro; el modo claro usa brandWeb[80]. |
| colorBrandForegroundLink | brandWeb[100] = #479ef5 | Rol de enlace oscuro; distinto del relleno del botón principal. |
## Microsoft Fluent 2 Guía completa del sistema de diseño

Una guía práctica para elegir, aplicar y revisar este sistema en una interfaz real.

Orientación completa, un punto de partida para la implementación y enlaces al código fuente.

## Sistema de diseño Microsoft Fluent 2 e implementación v9 de React

### Elige explícitamente la implementación web

> Fluent 2 es un lenguaje de diseño multiplataforma. Esta guía toma una decisión más específica y práctica: @fluentui/react-components, la biblioteca convergente React v9. El proveedor principal es FluentProvider, y este ejemplo usa webLightTheme o webDarkTheme. Un tema de Teams es una configuración de marca independiente; el conocido morado de Teams no es la escala azul predeterminada para la web. Los recursos nativos de Windows, iOS y Android tampoco son paquetes React intercambiables.

El ejemplo completo de abajo muestra una descripción del proyecto y una lista local de accesos. Combina Title3 y Body1 para establecer la jerarquía, TabList y Tab para ofrecer dos vistas, y Dialog con Field, Input y Select para tomar una decisión de acceso. El código no afirma que invite a una persona real. Esta separación permite demostrar el comportamiento de los componentes sin inventar un servicio para compartir.

Consulta la especificación complementaria

### Qué proporciona realmente el proveedor

FluentProvider coloca las variables del tema en su subárbol. Una superficie personalizada con el estilo tokens.colorNeutralBackground2 sigue el tema seleccionado, mientras que una superficie con el color blanco codificado no lo hace. Coloca el proveedor por encima de los componentes que necesitan el tema. La biblioteca aplica estilos a sus componentes mediante su entorno de ejecución de estilos; el ejemplo no te pide importar un archivo fluent.css inexistente.

Consulta la especificación complementaria

## Colores semánticos del sistema de diseño Microsoft Fluent 2 y la escala de marca web real

### Resuelve los alias según la marca elegida

La fuente de alias del tema claro asigna colorBrandBackground a brand[80]. El tema web proporciona brandWeb, donde el paso 80 es #0f6cbd. Por lo tanto, #0f6cbd es el relleno principal resultante en este ejemplo. El archivo de alias contiene comentarios ilustrativos antiguos con valores hexadecimales; interpretar esos comentarios como la paleta web actual daría un resultado incorrecto. El valor se establece mediante la relación de origen, no mediante el comentario.

Usa colorNeutralForeground1 para la descripción, colorNeutralForeground2 para las etiquetas complementarias y colorNeutralBackground3 para el espacio de trabajo circundante. Una acción principal puede usar colorBrandBackground con colorNeutralForegroundOnBrand. Los nombres de los roles siguen siendo el contrato cuando cambia el tema. El permiso de una persona se representa con texto; un borde azul o un indicador azul en una pestaña expresa la selección, no la autorización.

Consulta la especificación complementaria

### El modo oscuro cambia la correspondencia

En el modo oscuro, la superficie principal se resuelve en #292929 y el texto principal en #ffffff. El relleno principal de marca usa el paso 70, mientras que el color de primer plano de los enlaces usa el paso 100. Por eso, la vista previa cambia conjuntamente los roles de los tokens en lugar de limitarse a invertir el fondo de la página. Al alternar el tema, compara el cuadro de diálogo de acceso, la pestaña seleccionada y los metadatos atenuados: todos forman parte del mismo límite temático.

Consulta la especificación complementaria

## Tipografía del sistema de diseño Microsoft Fluent 2: asigna roles de texto a tokens de tiempo de ejecución

### Usa valores emparejados de tamaño y altura de línea

Body1 es el estilo de lectura base: 14px con una altura de línea de 20px. Caption1 es 12/16, Subtitle2 es 16/22 con grosor semibold y Title3 es 24/32 con grosor semibold. Estos pares crean una jerarquía diferenciada de títulos, etiquetas y cuerpo de texto sin tamaños intermedios arbitrarios. La tabla de arriba incluye los pasos más grandes como referencia, pero un espacio de trabajo compacto no necesita un titular de tamaño de presentación.

La fuente de tokens React y el sitio web de diseño discrepan actualmente en un detalle útil: fontSizeBase500 / lineHeightBase500 es 20/28 en la fuente de tiempo de ejecución, mientras que el gráfico de diseño web indica Subtitle1 en 20/26. Este ejemplo sigue el par de tiempo de ejecución. Considera estas diferencias una comprobación de versión e implementación, en lugar de combinar sin indicarlo los números que mejor encajen en la maqueta.

Consulta la especificación complementaria

### Estilos predefinidos de texto y pila de fuentes

Usa la semántica HTML junto con el estilo visual predefinido: Title3 as="h1" es un encabezado; aplicar estilos por sí solo no crea la estructura del documento. La pila base empieza con Segoe UI e incluye alternativas para distintas plataformas. No incluyas una fuente propietaria solo para que una captura de pantalla coincida. El ejemplo usa la pila documentada, por lo que los glifos exactos dependen de las fuentes instaladas en el dispositivo donde se visualice.

Para texto personalizado, typographyStyles.body1 agrupa la familia, el tamaño, el grosor y la altura de línea. Prefiere esa combinación en lugar de copiar solo fontSizeBase300. La lista de accesos usa direcciones de correo legibles que se ajustan a varias líneas; la etiqueta del permiso usa énfasis semibold, pero no compite con el título de la página.

Consulta la especificación complementaria

## Espaciado, forma y densidad en un espacio de trabajo del sistema de diseño Microsoft Fluent 2

### Los nombres de espaciado de React no son los nombres de la herramienta de diseño

Las directrices generales de diseño Fluent usan nombres de tamaño globales y un ritmo de cuatro unidades, que incluye 2, 6 y 10 para los ajustes de alineación. React ofrece alias específicos por eje: spacingHorizontalS es 8px, M es 12px, L es 16px y XXL es 24px, con nombres verticales equivalentes. No traduzcas size120 como spacingHorizontal120; ese identificador de React no existe.

Esta composición utiliza un relleno de 24px en el panel, una separación de 16px entre los campos del formulario y un espaciado de 12px para las filas de acceso. El ancho exacto de la página y la disposición en dos columnas son decisiones de la aplicación, no puntos de quiebre oficiales de Fluent. En una pantalla estrecha, el contexto complementario del equipo pasa debajo de la descripción, mientras las dos etiquetas de pestaña siguen visibles.

Consulta la especificación complementaria

### La forma describe la escala del componente

Los tokens de radio disponibles incluyen Medium, de 4px, y XLarge, de 8px, pero su disponibilidad no significa que todos los controles deban tener el mismo radio. El ejemplo de HTML utiliza una forma de control de 4px y una superficie de documento de 8px como decisiones explícitas para este estudio. Los componentes nativos deben conservar su anatomía predeterminada compatible, a menos que un requisito de diseño justifique una modificación. No accedas a los nombres de clase generados para imponer una forma diferente.

Consulta la especificación complementaria

## Sistema de diseño Microsoft Fluent 2 TabList: selección, paneles y comportamiento del teclado

### El valor seleccionado es estado de la aplicación

El ejemplo mantiene tab en el estado de React y lo pasa a selectedValue. onTabSelect recibe data.value, que actualiza ese estado. Cada Tab tiene un valor estable y apunta a su panel mediante aria-controls. Cada panel tiene role="tabpanel", aria-labelledby y una condición hidden. El componente no proporciona la descripción del proyecto ni la lista de accesos; estos elementos se representan explícitamente.

API separa el enfoque de la selección: selectTabOnFocus tiene el valor predeterminado false. En el ejemplo independiente, las teclas Left/Right y Home/End mueven el foco, y Enter o Space activa la pestaña enfocada. Este comportamiento de activación manual permite revisar las opciones de pestaña sin provocar un cambio inesperado de contenido. Mantén cortas las etiquetas de las pestañas; las directrices oficiales de diseño indican que las pestañas horizontales no se ajustan automáticamente ni tienen desplazamiento.

Consulta la especificación complementaria

### Elige otro patrón cuando aumente el número de categorías

Descripción y Personas son dos vistas estrechamente relacionadas de un mismo espacio de trabajo, por lo que las pestañas son adecuadas. Una lista de todos los proyectos de la organización sería navegación, no otra fila de pestañas. Si las categorías ya no caben en un ancho reducido, diseña deliberadamente un patrón de desbordamiento u otra forma de selección; reducir las etiquetas hasta volverlas ilegibles no es una estrategia adaptable.

Consulta la especificación complementaria

## Campo del sistema de diseño Fluent de Microsoft Fluent 2: etiquetas accesibles y validación significativa

### Un campo es una composición, no una entrada con estilos

Field combina un espacio para la etiqueta, el control secundario, una indicación opcional y un mensaje de validación. Su contrato documentado para el control incluye id, aria-labelledby, aria-describedby, aria-invalid y aria-required. Los controles de entrada de Fluent pueden ser elementos secundarios directos; los controles personalizados pueden consumir las propiedades de la función de renderizado. Por eso, envolver el componente Input de correo electrónico con Field resulta más útil que colocar texto sin relación encima de una entrada.

En el ejemplo, validationState solo tiene el valor error después de enviar un valor no válido. La aplicación comprueba que el valor tenga formato de correo electrónico y que no haya duplicados en su lista en memoria. Conserva el valor introducido y proporciona una indicación para corregirlo mediante validationMessage. Field presenta y vincula ese resultado; no determina si la dirección existe, envía una invitación ni concede acceso.

Consulta la especificación complementaria

### Mantén el estado de validación separado del permiso

El componente Select de permisos contiene Puede ver y Puede editar, mientras que el campo de correo electrónico tiene su propio estado de error. Evita que todo el cuadro de diálogo se vuelva rojo porque una dirección esté incompleta. required es una indicación accesible, no un sustituto de la autorización o validación del servidor. El formulario de ejemplo usa noValidate para mostrar de forma coherente su mensaje de validación explícito; un servicio de producción debe volver a validar la solicitud.

- Dirección vacía o con formato incorrecto: mantén abierto el cuadro de diálogo y explica el formato aceptado.

- Dirección que ya está en la lista de accesos local: muestra un mensaje específico sobre el duplicado sin añadir otra fila.

- Dirección nueva y válida: añade localmente el permiso elegido, cierra el cuadro de diálogo y muestra un resultado que indique con claridad que solo se ha aplicado de forma local.

Consulta la especificación complementaria

## Cuadro de diálogo del sistema de diseño Fluent de Microsoft Fluent 2: un formulario dentro de la estructura compatible

### Mantén el botón de envío dentro del formulario

La composición consta de Dialog, DialogTrigger, DialogSurface, un elemento form y DialogBody, que contiene DialogTitle, DialogContent y DialogActions. El formulario envuelve el cuerpo para que el botón de envío del pie participe en el envío. Cancel tiene type="button" y se encuentra dentro de un DialogTrigger que cierra el cuadro; de lo contrario, el tipo predeterminado del botón podría enviar el formulario accidentalmente.

open y onOpenChange controlan la visibilidad. disableButtonEnhancement impide que el disparador aplique mejoras redundantes a un Button de Fluent que ya proporciona el comportamiento de botón. El controlador de éxito cierra el cuadro solo después de que la validación local se complete correctamente. Si la validación falla, el mismo correo electrónico y permiso siguen disponibles para corregirlos.

Consulta la especificación complementaria

### Elige el comportamiento modal según la decisión real

Esta decisión breve sobre el acceso utiliza el tipo modal predeterminado. Un cuadro de diálogo no modal permitiría trabajar en segundo plano, mientras que un cuadro de diálogo de alerta está pensado para decisiones urgentes y tiene un comportamiento de cierre más estricto. Un formulario de acceso normal no necesita semántica de alerta. Las directrices oficiales indican que el foco debe estar dentro del cuadro de diálogo y volver a su disparador al cerrarlo; la implementación independiente utiliza el elemento nativo dialog y restablece explícitamente el foco en su disparador.

Consulta la especificación complementaria

## Estilos personalizados del sistema de diseño Fluent de Microsoft Fluent 2 sin alterar el tema

### Usa makeStyles y variables semánticas

El código completo utiliza makeStyles con tokens para las superficies, las separaciones y el radio. Así, el panel personalizado mantiene la coherencia con el proveedor. El nombre de clase generado es un detalle de implementación; no lo copies de DevTools en una hoja de estilos. Para probar otros temas, cambia el tema del proveedor y examina conjuntamente los controles nativos y las áreas personalizadas.

La escala de duración publicada va de 50ms a 500ms. El ejemplo HTML usa durationFast, de 150ms, para cambios sencillos al pasar el cursor y elimina esas transiciones cuando está activo prefers-reduced-motion. Es una decisión de implementación que utiliza un token disponible, no una afirmación de que todas las interacciones de Fluent deban durar 150ms. El contenido y los mensajes de error siguen siendo comprensibles aunque se elimine el movimiento.

Consulta la especificación complementaria

### Pon a prueba los límites de una interpretación

El ejemplo integrado está etiquetado intencionalmente como HTML/CSS, no como una implementación nativa de Fluent. Muestra los valores verificados y la interacción del cuadro de diálogo de acceso sin descargar un framework en la vista previa. Usa el ejemplo completo de React para evaluar el estilo nativo, los portales, la gestión del foco y las actualizaciones de componentes. Superar las comprobaciones de interacción independientes es evidencia a favor de este ejemplo, no una certificación de una aplicación de uso compartido lista para producción.

Consulta la especificación complementaria

## Sistema de diseño Microsoft Fluent 2: instala y verifica la composición completa

### Un punto de entrada ejecutable, no un esquema de tareas

La configuración está pensada para una aplicación React existente que tenga un elemento con id root. Instala el conjunto v9 y luego añade la entrada JSX a la aplicación. Se incluyen juntos los imports, el proveedor, el árbol de componentes, los controladores, el estado y el renderizado en la raíz. No se invoca ningún servicio externo para compartir API. Si tu plantilla inicial ya renderiza App, conserva su entrada raíz y exporta App en lugar de crear una segunda raíz.

Registra la versión resuelta del paquete en el archivo de bloqueo. Las referencias al código fuente enlazadas describen la implementación de React v9 que se inspeccionó, mientras que las URL de master pueden cambiar. Antes de actualizar, compara el tema exportado y el comportamiento exacto de Field, TabList y Dialog que se usan aquí. La configuración evita deliberadamente combinar componentes de v8 @fluentui/react con ejemplos de v9.

1. Instala @fluentui/react-components@9 en el proyecto React existente y conserva el archivo de bloqueo generado.

2. Renderiza la entrada proporcionada dentro de un único FluentProvider, con webLightTheme como tema inicial.

3. Abre el cuadro de diálogo, envía una dirección no válida, corrígela y confirma que la persona añadida aparezca únicamente en el estado local.

4. Repite la prueba con webDarkTheme y navegación mediante teclado; verifica la asociación del panel, el anuncio del error y la devolución del foco.

Consulta la especificación complementaria

### Qué conectar en un producto real para compartir

Sustituye la adición local solo después de definir el contrato del servicio: actor autenticado, espacio de trabajo de destino, valores de permiso permitidos, resultado de la invitación y comportamiento ante duplicados. Mantén abierto el cuadro de diálogo si el servicio rechaza una solicitud y muestra el resultado confirmado en lugar de afirmar de forma optimista que se concedió el acceso. Estas reglas no forman parte de Fluent, pero el código deja explícito el punto de integración en vez de ocultarlo tras un botón de envío genérico.

Consulta la especificación complementaria

## Ejemplos de componentes del sistema de diseño Microsoft Fluent 2

### Muestra interactiva independiente completa

Guarda el documento completo como index.html y ábrelo en un navegador. Interpretación independiente en HTML/CSS de los tokens web de Fluent UI React v9 y de la estructura documentada de Field, TabList y Dialog. No es un entorno de ejecución nativo de componentes Fluent.

```
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Fluent web workspace study</title><style>
:root{color-scheme:light;--colorNeutralForeground1:#242424;--colorNeutralForeground2:#424242;--colorNeutralForeground3:#616161;--colorNeutralBackground1:#fff;--colorNeutralBackground2:#fafafa;--colorNeutralBackground3:#f5f5f5;--colorNeutralStroke1:#d1d1d1;--colorNeutralStroke2:#e0e0e0;--colorBrandBackground:#0f6cbd;--colorBrandBackgroundHover:#115ea3;--colorBrandBackgroundPressed:#0c3b5e;--colorBrandBackground2:#ebf3fc;--colorBrandForegroundLink:#115ea3;--colorNeutralForegroundOnBrand:#fff;--borderRadiusMedium:4px;--borderRadiusXLarge:8px;--spacingHorizontalS:8px;--spacingHorizontalM:12px;--spacingHorizontalL:16px;--spacingHorizontalXXL:24px;--durationFast:150ms}
[data-theme=dark]{color-scheme:dark;--colorNeutralForeground1:#fff;--colorNeutralForeground2:#d6d6d6;--colorNeutralForeground3:#adadad;--colorNeutralBackground1:#292929;--colorNeutralBackground2:#1f1f1f;--colorNeutralBackground3:#141414;--colorNeutralStroke1:#666;--colorNeutralStroke2:#525252;--colorBrandBackground:#115ea3;--colorBrandBackgroundHover:#0f6cbd;--colorBrandBackground2:#082338;--colorBrandForegroundLink:#479ef5}
*{box-sizing:border-box}body{margin:0;background:var(--colorNeutralBackground3);color:var(--colorNeutralForeground1);font-family:'Segoe UI','Segoe UI Web (West European)',-apple-system,BlinkMacSystemFont,Roboto,'Helvetica Neue',sans-serif;font-size:14px;line-height:20px}button,input,select{font:inherit}button{cursor:pointer;min-height:32px;padding:5px 12px;border:1px solid var(--colorNeutralStroke1);border-radius:var(--borderRadiusMedium);background:var(--colorNeutralBackground1);color:var(--colorNeutralForeground1);font-weight:600;transition:background var(--durationFast)}button:hover{background:var(--colorNeutralBackground3)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--colorBrandForegroundLink);outline-offset:3px}.primary{background:var(--colorBrandBackground);color:var(--colorNeutralForegroundOnBrand);border-color:transparent}.primary:hover{background:var(--colorBrandBackgroundHover)}.primary:active{background:var(--colorBrandBackgroundPressed)}.topbar{display:flex;justify-content:space-between;align-items:center;padding:12px 24px;background:var(--colorNeutralBackground1);border-bottom:1px solid var(--colorNeutralStroke2);gap:16px}.brand{display:flex;align-items:center;gap:12px;font-weight:600}.mark{display:grid;grid-template-columns:repeat(2,7px);gap:3px}.mark i{width:7px;height:7px;background:var(--colorBrandBackground)}.top-note{font-size:12px;color:var(--colorNeutralForeground3)}.page{max-width:1100px;margin:auto;padding:24px}.heading{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:16px}.eyebrow{font-size:12px;line-height:16px;color:var(--colorNeutralForeground3);margin-bottom:4px}h1{font-size:24px;line-height:32px;font-weight:600;margin:0 0 4px}h2{font-size:20px;line-height:28px;font-weight:600;margin:0 0 16px}h3{font-size:16px;line-height:22px;font-weight:600;margin:20px 0 8px}p{margin:0 0 12px}.muted{color:var(--colorNeutralForeground3)}.tabs{display:flex;gap:16px;border-bottom:1px solid var(--colorNeutralStroke2);margin-bottom:24px}.tab{position:relative;background:none;border:0;border-radius:0;font-weight:400;padding:12px 4px;color:var(--colorNeutralForeground2)}.tab[aria-selected=true]{font-weight:600;color:var(--colorBrandForegroundLink)}.tab[aria-selected=true]:after{content:'';position:absolute;bottom:0;left:4px;right:4px;height:3px;background:var(--colorBrandBackground);border-radius:3px}.layout{display:grid;grid-template-columns:minmax(0,1fr) 240px;gap:24px}.document,.aside,.people{background:var(--colorNeutralBackground1);border:1px solid var(--colorNeutralStroke2);border-radius:var(--borderRadiusXLarge);padding:24px}.document{max-width:720px}.docmeta{display:flex;gap:8px;align-items:center;color:var(--colorNeutralForeground3);font-size:12px;line-height:16px;margin-bottom:24px}.avatar{flex:none;display:grid;place-items:center;background:var(--colorBrandBackground2);color:var(--colorBrandForegroundLink);font-weight:600;width:32px;height:32px;border-radius:50%}.quote{padding:12px 16px;background:var(--colorNeutralBackground2);border-left:3px solid var(--colorBrandBackground);margin:16px 0}.checklist{list-style:none;padding:0;margin:12px 0}.checklist li{display:flex;gap:8px;margin:8px 0}.checklist input{accent-color:var(--colorBrandBackground);width:16px;height:16px;margin-top:2px}.aside{padding:20px;height:fit-content}.aside h2{font-size:16px;line-height:22px;margin-bottom:16px}.person{display:flex;gap:8px;align-items:center;margin-bottom:16px}.person p{margin:0}.small{font-size:12px;line-height:16px}.aside hr{border:0;border-top:1px solid var(--colorNeutralStroke2);margin:20px 0}.tag{display:inline-block;border:1px solid var(--colorNeutralStroke1);padding:2px 8px;border-radius:4px;font-size:12px;color:var(--colorNeutralForeground2)}.people-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px}.people-head h2{margin:0}.people ul{list-style:none;padding:0;margin:0}.member{display:flex;align-items:center;gap:12px;padding:16px 0;border-top:1px solid var(--colorNeutralStroke2)}.member-info{flex:1;min-width:0;overflow-wrap:anywhere}.member-info p{margin:0}.member .tag{flex:none}.caption{margin:20px 0 0;color:var(--colorNeutralForeground3);font-size:12px;line-height:16px}.status{margin-top:16px;overflow-wrap:anywhere;color:var(--colorNeutralForeground2)}.status:empty{display:none}[hidden]{display:none!important}dialog{width:min(520px,calc(100vw - 32px));padding:24px;border:1px solid var(--colorNeutralStroke1);border-radius:8px;background:var(--colorNeutralBackground1);color:var(--colorNeutralForeground1);max-height:calc(100dvh - 32px);overflow:auto}dialog::backdrop{background:rgba(0,0,0,.4)}dialog h2{font-size:20px;line-height:28px;margin-bottom:12px}.field{margin:20px 0}.field label{display:block;font-weight:600;margin-bottom:8px}.required{color:#b10e1e}.field input,.field select{width:100%;min-height:36px;background:var(--colorNeutralBackground1);color:var(--colorNeutralForeground1);padding:7px 12px;border:1px solid var(--colorNeutralStroke1);border-bottom-color:var(--colorNeutralForeground3);border-radius:4px}.field input:focus{border-bottom:2px solid var(--colorBrandBackground);padding-bottom:6px}.hint{font-size:12px;line-height:16px;color:var(--colorNeutralForeground3);margin-top:8px}.error{font-size:12px;line-height:16px;color:#b10e1e;margin-top:8px}.field input[aria-invalid=true]{border-color:#b10e1e}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}[data-theme=dark] .error,[data-theme=dark] .required{color:#ff99a4}footer{padding:0 24px 20px;text-align:center;font-size:12px;color:var(--colorNeutralForeground3)}@media(max-width:700px){.page{padding:16px}.topbar{padding:12px 16px}.top-note{display:none}.layout{grid-template-columns:1fr}.document,.people{padding:20px}.aside{display:grid;grid-template-columns:1fr 1fr;gap:8px 16px}.aside h2,.aside hr{grid-column:1/-1}.aside hr{margin:0}.heading{align-items:flex-start}.heading .primary{flex:none}.member{flex-wrap:wrap}.member .tag{margin-left:44px}.topbar button,.heading button{min-height:40px}}@media(max-width:420px){h1{font-size:20px;line-height:28px}.heading{flex-direction:column;gap:12px}.aside{display:block}.member .tag{margin-left:0}}@media(prefers-reduced-motion:reduce){*{transition:none!important}}
</style></head><body>
<header class="topbar"><div class="brand"><span class="mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>Northstar workspace <span class="top-note">/ Product team</span></div><button id="theme" aria-pressed="false">Dark theme</button></header>
<main class="page"><div class="heading"><div><div class="eyebrow">Team workspace · October launch</div><h1>Launch workspace</h1><p class="muted">One brief. A clear decision. The right people.</p></div><button class="primary" id="share">Add a person</button></div>
<div class="tabs" role="tablist" aria-label="Workspace views"><button class="tab" id="brief-tab" role="tab" aria-controls="brief-panel" aria-selected="true" tabindex="0">Brief</button><button class="tab" id="people-tab" role="tab" aria-controls="people-panel" aria-selected="false" tabindex="-1">People <span id="count">2</span></button></div>
<section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab" tabindex="0"><div class="layout"><article class="document"><div class="docmeta"><span class="avatar" aria-hidden="true">AC</span><span>Alex Chen · Product design<br>Updated today at 09:30</span></div><h2>A more welcoming first project</h2><p>Help a new team move from an empty workspace to its first useful result. This review focuses on the welcome screen and the first-project setup.</p><h3>The decision</h3><div class="quote">Should a sample project be created automatically, or offered as an option?</div><p>We want a clear starting point without filling the workspace with content someone did not choose. Review both paths before the team walkthrough.</p><h3>Review checklist</h3><ul class="checklist"><li><input id="c1" type="checkbox" checked><label for="c1">Welcome copy explains the next step</label></li><li><input id="c2" type="checkbox"><label for="c2">Empty workspace offers a sample project</label></li><li><input id="c3" type="checkbox"><label for="c3">Keyboard path reaches the first project action</label></li></ul><p class="small muted">Checklist changes are local to this example.</p></article><aside class="aside"><h2>Working together</h2><div class="person"><span class="avatar" aria-hidden="true">AC</span><div><p>Alex Chen</p><span class="small muted">Workspace owner</span></div></div><div class="person"><span class="avatar" aria-hidden="true">MP</span><div><p>Maya Patel</p><span class="small muted">Can edit</span></div></div><hr><div><h3>Next review</h3><p>Thursday, 10:00<br><span class="muted small">Team walkthrough · UTC</span></p></div><div><h3>Access</h3><span class="tag">Specific people</span><p class="small muted" style="margin-top:8px">Adding a person here updates only this local preview.</p></div></aside></div></section>
<section id="people-panel" role="tabpanel" aria-labelledby="people-tab" tabindex="0" hidden><div class="people"><div class="people-head"><h2>People with access</h2><span class="tag">Local example</span></div><p class="muted">The roles below are fictional. No invitation or real access change is made.</p><ul id="members"><li class="member"><span class="avatar" aria-hidden="true">AC</span><div class="member-info"><p>Alex Chen</p><span class="small muted">alex@example.com</span></div><span class="tag">Owner</span></li><li class="member"><span class="avatar" aria-hidden="true">MP</span><div class="member-info"><p>Maya Patel</p><span class="small muted">maya@example.com</span></div><span class="tag">Can edit</span></li></ul></div></section><p class="status" id="status" role="status"></p><p class="caption">Fluent UI React v9 web-token study · HTML/CSS interpretation, not native library components. Try tabs, theme, checkboxes and the access form.</p></main>
<dialog id="access" aria-labelledby="dialog-title"><form id="access-form" novalidate><h2 id="dialog-title">Add a person to Launch workspace</h2><p class="muted">Choose an address and permission. This demonstration updates the local list only.</p><div class="field"><label for="email">Email address <span class="required" aria-hidden="true">*</span></label><input id="email" type="email" autocomplete="email" aria-required="true" aria-describedby="email-hint" autofocus><div class="hint" id="email-hint">For example, jordan@example.com</div><div class="error" id="error" role="alert" hidden></div></div><div class="field"><label for="permission">Permission</label><select id="permission" aria-describedby="permission-hint"><option>Can view</option><option>Can edit</option></select><div class="hint" id="permission-hint">Viewers can read; editors can change the brief in a real application</div></div><div class="actions"><button type="button" id="cancel">Cancel</button><button class="primary" type="submit">Add locally</button></div></form></dialog>
<script>
const $=id=>document.getElementById(id),tabs=[...document.querySelectorAll('[role=tab]')],emails=new Set(['alex@example.com','maya@example.com']);
function selectTab(tab){tabs.forEach(t=>{const chosen=t===tab;t.setAttribute('aria-selected',String(chosen));t.tabIndex=chosen?0:-1;$(t.getAttribute('aria-controls')).hidden=!chosen;});}
tabs.forEach((tab,index)=>{tab.addEventListener('click',()=>selectTab(tab));tab.addEventListener('keydown',event=>{let next;if(event.key==='ArrowRight')next=(index+1)%tabs.length;if(event.key==='ArrowLeft')next=(index+tabs.length-1)%tabs.length;if(event.key==='Home')next=0;if(event.key==='End')next=tabs.length-1;if(next!==undefined){event.preventDefault();tabs.forEach(t=>t.tabIndex=-1);tabs[next].tabIndex=0;tabs[next].focus();}});});
$('theme').onclick=()=>{const dark=document.documentElement.dataset.theme!=='dark';document.documentElement.dataset.theme=dark?'dark':'light';$('theme').textContent=dark?'Light theme':'Dark theme';$('theme').setAttribute('aria-pressed',String(dark));};
const dialog=$('access'),email=$('email'),error=$('error');function clearError(){error.hidden=true;error.textContent='';email.removeAttribute('aria-invalid');email.setAttribute('aria-describedby','email-hint');}function showError(message){error.textContent=message;error.hidden=false;email.setAttribute('aria-invalid','true');email.setAttribute('aria-describedby','email-hint error');email.focus();}
$('share').onclick=()=>{clearError();dialog.showModal();email.focus();};$('cancel').onclick=()=>dialog.close();dialog.addEventListener('close',()=>$('share').focus());dialog.addEventListener('click',event=>{if(event.target===dialog){const r=dialog.getBoundingClientRect();if(event.clientX<r.left||event.clientX>r.right||event.clientY<r.top||event.clientY>r.bottom)dialog.close();}});email.addEventListener('input',clearError);
$('access-form').addEventListener('submit',event=>{event.preventDefault();const value=email.value.trim().toLowerCase();if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)){showError('Enter an email address such as name@example.com');return;}if(emails.has(value)){showError('This address already appears in the local access list');return;}const permission=$('permission').value;emails.add(value);const item=document.createElement('li');item.className='member';const avatar=document.createElement('span');avatar.className='avatar';avatar.setAttribute('aria-hidden','true');avatar.textContent=value.slice(0,2).toUpperCase();const info=document.createElement('div');info.className='member-info';const name=document.createElement('p');name.textContent=value;const sub=document.createElement('span');sub.className='small muted';sub.textContent='Added in this local example';info.append(name,sub);const role=document.createElement('span');role.className='tag';role.textContent=permission;item.append(avatar,info,role);$('members').append(item);$('count').textContent=emails.size;$('status').textContent='Added '+value+' as '+permission.toLowerCase()+' locally. No invitation was sent.';email.value='';clearError();selectTab($('people-tab'));dialog.close();});
</script></body></html>
```

Referencia de código

- El cuadro de diálogo de acceso y las pestañas de este documento usan controles nativos de HTML; ejecuta la entrada independiente de React v9 para evaluar el comportamiento de FluentProvider, Field y Dialog.

### Entrada de implementación nativa completa

La implementación completa de la configuración, reunida aquí junto con las explicaciones específicas de API. Sigue los requisitos documentados de paquetes y compilación en la sección de configuración.

```
// Terminal: npm install @fluentui/react-components@9
// Existing React application; index.html must contain <div id="root"></div>.
// Entry file: src/main.jsx
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import {
  FluentProvider, webLightTheme, webDarkTheme, makeStyles, tokens,
  Button, Body1, Title3, TabList, Tab, Field, Input, Select,
  Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle,
  DialogContent, DialogActions
} from '@fluentui/react-components';

const useStyles = makeStyles({
  page: { maxWidth: '880px', margin: '0 auto', padding: tokens.spacingHorizontalXXL },
  header: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: tokens.spacingHorizontalL },
  panel: { padding: tokens.spacingHorizontalXXL, marginTop: tokens.spacingVerticalL,
    backgroundColor: tokens.colorNeutralBackground2, borderRadius: tokens.borderRadiusXLarge },
  form: { display: 'grid', gap: tokens.spacingVerticalL },
  row: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: tokens.spacingHorizontalM,
    paddingTop: tokens.spacingVerticalM, paddingBottom: tokens.spacingVerticalM },
});

function Workspace({ dark, setDark }) {
  const styles = useStyles();
  const [tab, setTab] = React.useState('brief');
  const [open, setOpen] = React.useState(false);
  const [email, setEmail] = React.useState('');
  const [permission, setPermission] = React.useState('Can view');
  const [error, setError] = React.useState('');
  const [notice, setNotice] = React.useState('');
  const [people, setPeople] = React.useState([{ email: 'alex@example.com', permission: 'Owner' }]);
  function submit(event) {
    event.preventDefault();
    const value = email.trim().toLowerCase();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      setError('Enter an email address such as name@example.com'); return;
    }
    if (people.some(person => person.email === value)) {
      setError('This address already appears in the local access list'); return;
    }
    setPeople(previous => [...previous, { email: value, permission }]);
    setNotice(`Added ${value} as ${permission.toLowerCase()} in this local example. No invitation was sent.`);
    setEmail(''); setError(''); setOpen(false); setTab('people');
  }
  return <main className={styles.page}>
    <div className={styles.header}>
      <Title3 as="h1">Launch workspace</Title3>
      <Button onClick={() => setDark(!dark)}>{dark ? 'Light theme' : 'Dark theme'}</Button>
    </div>
    <Body1>Project brief and access settings · local demonstration</Body1>
    <TabList aria-label="Workspace views" selectedValue={tab}
      onTabSelect={(_, data) => setTab(data.value)}>
      <Tab id="brief-tab" value="brief" aria-controls="brief-panel">Brief</Tab>
      <Tab id="people-tab" value="people" aria-controls="people-panel">People</Tab>
    </TabList>
    <section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab"
      hidden={tab !== 'brief'} className={styles.panel} tabIndex={0}>
      <h2>Launch a clearer onboarding flow</h2>
      <p>Review the welcome screen and first-project setup before the team walkthrough.</p>
      <p>Decision needed: should the sample project be created automatically or offered as an option?</p>
    </section>
    <section id="people-panel" role="tabpanel" aria-labelledby="people-tab"
      hidden={tab !== 'people'} className={styles.panel} tabIndex={0}>
      <h2>People with access</h2>
      <ul>{people.map(person => <li key={person.email} className={styles.row}>
        <span>{person.email}</span><strong>{person.permission}</strong>
      </li>)}</ul>
    </section>
    <Dialog open={open} onOpenChange={(_, data) => setOpen(data.open)}>
      <DialogTrigger disableButtonEnhancement><Button appearance="primary">Add a person</Button></DialogTrigger>
      <DialogSurface>
        <form noValidate onSubmit={submit}>
          <DialogBody>
            <DialogTitle>Add a person to Launch workspace</DialogTitle>
            <DialogContent className={styles.form}>
              <p>This example updates local state only. It does not send email or change real access.</p>
              <Field label="Email address" required validationState={error ? 'error' : 'none'}
                validationMessage={error || undefined}>
                <Input type="email" value={email} onChange={(_, data) => { setEmail(data.value); setError(''); }} />
              </Field>
              <Field label="Permission" hint="Viewers can read; editors can change the brief in a real application">
                <Select value={permission} onChange={(_, data) => setPermission(data.value)}>
                  <option>Can view</option><option>Can edit</option>
                </Select>
              </Field>
            </DialogContent>
            <DialogActions>
              <DialogTrigger disableButtonEnhancement><Button type="button">Cancel</Button></DialogTrigger>
              <Button type="submit" appearance="primary">Add locally</Button>
            </DialogActions>
          </DialogBody>
        </form>
      </DialogSurface>
    </Dialog>
    <p role="status">{notice}</p>
  </main>;
}
function App() {
  const [dark, setDark] = React.useState(false);
  return <FluentProvider theme={dark ? webDarkTheme : webLightTheme}>
    <Workspace dark={dark} setDark={setDark} />
  </FluentProvider>;
}
createRoot(document.getElementById('root')).render(<App />);
```

Referencia de código

- Composición completa de componentes de React v9 para una aplicación React existente. La vista previa independiente es una interpretación aparte en HTML/CSS. Este código solo realiza validaciones locales y actualizaciones de estado; no envía correos electrónicos ni modifica permisos reales. Se comprobaron las API de origen; no se afirma que la compilación con el paquete nativo funcione hasta probarla en el proyecto de destino.

### Field + Input + Select: un formulario de acceso con etiquetas

Estos fragmentos usan las variables de estado y los imports de la configuración completa que aparece a continuación. Field se encarga de mostrar la etiqueta, la indicación y la validación; la aplicación se encarga de la regla para el correo electrónico y de comprobar si hay duplicados.

```
<Field label="Email address" required
  validationState={error ? 'error' : 'none'}
  validationMessage={error || undefined}>
  <Input type="email" value={email}
    onChange={(_, data) => { setEmail(data.value); setError(''); }} />
</Field>
<Field label="Permission" hint="Choose what this person can do">
  <Select value={permission}
    onChange={(_, data) => setPermission(data.value)}>
    <option>Can view</option><option>Can edit</option>
  </Select>
</Field>
```

Referencia de código

- required proporciona una indicación accesible de que el campo es obligatorio; no implementa la regla de validación de la aplicación.

- El estado de validación predeterminado pasa a ser error cuando existe validationMessage. Establece none explícitamente cuando no deba mostrarse ningún error.

- El onChange de Input proporciona la cadena actual en data.value; no confundas su controlador con un evento de entrada del DOM sin más.

### TabList controlado con paneles asociados explícitamente

TabList controla la selección y el comportamiento de las pestañas. Renderiza los paneles de contenido por tu cuenta y mantén conectados sus ids y nombres accesibles con cada Tab.

```
<TabList selectedValue={tab} aria-label="Workspace views"
  onTabSelect={(_, data) => setTab(data.value)}>
  <Tab id="brief-tab" value="brief" aria-controls="brief-panel">Brief</Tab>
  <Tab id="people-tab" value="people" aria-controls="people-panel">People</Tab>
</TabList>
<section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab"
  hidden={tab !== 'brief'} tabIndex={0}>Project brief</section>
<section id="people-panel" role="tabpanel" aria-labelledby="people-tab"
  hidden={tab !== 'people'} tabIndex={0}>People with access</section>
```

Referencia de código

- selectedValue debe definirse en TabList; cada Tab tiene un valor distinto.

- selectTabOnFocus es false de forma predeterminada. El foco con las flechas y la activación son acciones independientes, salvo que se cambie deliberadamente ese comportamiento.

- Las pestañas horizontales de Fluent no se ajustan a varias líneas ni se desplazan automáticamente; el ejemplo usa dos etiquetas cortas para que sigan visibles.

## Configuración del agente del sistema de diseño Microsoft Fluent 2

### Punto de partida para la implementación

Composición completa de componentes de React v9 para una aplicación React existente. La vista previa independiente es una interpretación aparte en HTML/CSS. Este código solo realiza validaciones locales y actualizaciones de estado; no envía correos electrónicos ni modifica permisos reales. Se comprobaron las API de origen; no se afirma que la compilación con el paquete nativo funcione hasta probarla en el proyecto de destino.

```
// Terminal: npm install @fluentui/react-components@9
// Existing React application; index.html must contain <div id="root"></div>.
// Entry file: src/main.jsx
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import {
  FluentProvider, webLightTheme, webDarkTheme, makeStyles, tokens,
  Button, Body1, Title3, TabList, Tab, Field, Input, Select,
  Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle,
  DialogContent, DialogActions
} from '@fluentui/react-components';

const useStyles = makeStyles({
  page: { maxWidth: '880px', margin: '0 auto', padding: tokens.spacingHorizontalXXL },
  header: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: tokens.spacingHorizontalL },
  panel: { padding: tokens.spacingHorizontalXXL, marginTop: tokens.spacingVerticalL,
    backgroundColor: tokens.colorNeutralBackground2, borderRadius: tokens.borderRadiusXLarge },
  form: { display: 'grid', gap: tokens.spacingVerticalL },
  row: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: tokens.spacingHorizontalM,
    paddingTop: tokens.spacingVerticalM, paddingBottom: tokens.spacingVerticalM },
});

function Workspace({ dark, setDark }) {
  const styles = useStyles();
  const [tab, setTab] = React.useState('brief');
  const [open, setOpen] = React.useState(false);
  const [email, setEmail] = React.useState('');
  const [permission, setPermission] = React.useState('Can view');
  const [error, setError] = React.useState('');
  const [notice, setNotice] = React.useState('');
  const [people, setPeople] = React.useState([{ email: 'alex@example.com', permission: 'Owner' }]);
  function submit(event) {
    event.preventDefault();
    const value = email.trim().toLowerCase();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      setError('Enter an email address such as name@example.com'); return;
    }
    if (people.some(person => person.email === value)) {
      setError('This address already appears in the local access list'); return;
    }
    setPeople(previous => [...previous, { email: value, permission }]);
    setNotice(`Added ${value} as ${permission.toLowerCase()} in this local example. No invitation was sent.`);
    setEmail(''); setError(''); setOpen(false); setTab('people');
  }
  return <main className={styles.page}>
    <div className={styles.header}>
      <Title3 as="h1">Launch workspace</Title3>
      <Button onClick={() => setDark(!dark)}>{dark ? 'Light theme' : 'Dark theme'}</Button>
    </div>
    <Body1>Project brief and access settings · local demonstration</Body1>
    <TabList aria-label="Workspace views" selectedValue={tab}
      onTabSelect={(_, data) => setTab(data.value)}>
      <Tab id="brief-tab" value="brief" aria-controls="brief-panel">Brief</Tab>
      <Tab id="people-tab" value="people" aria-controls="people-panel">People</Tab>
    </TabList>
    <section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab"
      hidden={tab !== 'brief'} className={styles.panel} tabIndex={0}>
      <h2>Launch a clearer onboarding flow</h2>
      <p>Review the welcome screen and first-project setup before the team walkthrough.</p>
      <p>Decision needed: should the sample project be created automatically or offered as an option?</p>
    </section>
    <section id="people-panel" role="tabpanel" aria-labelledby="people-tab"
      hidden={tab !== 'people'} className={styles.panel} tabIndex={0}>
      <h2>People with access</h2>
      <ul>{people.map(person => <li key={person.email} className={styles.row}>
        <span>{person.email}</span><strong>{person.permission}</strong>
      </li>)}</ul>
    </section>
    <Dialog open={open} onOpenChange={(_, data) => setOpen(data.open)}>
      <DialogTrigger disableButtonEnhancement><Button appearance="primary">Add a person</Button></DialogTrigger>
      <DialogSurface>
        <form noValidate onSubmit={submit}>
          <DialogBody>
            <DialogTitle>Add a person to Launch workspace</DialogTitle>
            <DialogContent className={styles.form}>
              <p>This example updates local state only. It does not send email or change real access.</p>
              <Field label="Email address" required validationState={error ? 'error' : 'none'}
                validationMessage={error || undefined}>
                <Input type="email" value={email} onChange={(_, data) => { setEmail(data.value); setError(''); }} />
              </Field>
              <Field label="Permission" hint="Viewers can read; editors can change the brief in a real application">
                <Select value={permission} onChange={(_, data) => setPermission(data.value)}>
                  <option>Can view</option><option>Can edit</option>
                </Select>
              </Field>
            </DialogContent>
            <DialogActions>
              <DialogTrigger disableButtonEnhancement><Button type="button">Cancel</Button></DialogTrigger>
              <Button type="submit" appearance="primary">Add locally</Button>
            </DialogActions>
          </DialogBody>
        </form>
      </DialogSurface>
    </Dialog>
    <p role="status">{notice}</p>
  </main>;
}
function App() {
  const [dark, setDark] = React.useState(false);
  return <FluentProvider theme={dark ? webDarkTheme : webLightTheme}>
    <Workspace dark={dark} setDark={setDark} />
  </FluentProvider>;
}
createRoot(document.getElementById('root')).render(<App />);
```

### Una instrucción para tu proyecto

```
Usa Fluent UI React v9 desde @fluentui/react-components. Renderiza FluentProvider con webLightTheme y un control para alternar a webDarkTheme. Compón la descripción con Title3 y Body1, vistas controladas de TabList/Tab con paneles explícitos y un cuadro de diálogo de acceso con Field/Input/Select y un formulario real. Usa tokens para las superficies personalizadas y el espaciado. Sigue el ejemplo de componentes proporcionado y los pares de tipos en tiempo de ejecución, incluido Subtitle1 en 20/28. Conserva las entradas no válidas, rechaza las direcciones duplicadas locales, devuelve el foco al cuadro de diálogo y describe con precisión las adiciones locales. Conecta un servicio para compartir solo cuando se proporcione su contrato de autorización y resultados.
```

## Fuentes y alcance del sistema de diseño Microsoft Fluent 2

Un estudio de una aplicación OpenDesign de Fluent UI React v9; no es un producto de Microsoft ni un servicio activo para compartir. La vista previa independiente es una interpretación HTML/CSS de los tokens web verificados y de la estructura de los componentes; el código React de abajo usa componentes nativos de Fluent. Los valores de referencia corresponden a la instantánea del código fuente enlazado que se comprobó el 3 de octubre de 2026; no garantizan que todas las versiones, plataformas o marcas usen valores idénticos.

- Punto de entrada del paquete React v9 Proveedor e instalación del conjunto.

- Arquitectura de temas Uso de tokens semánticos y temas disponibles.

- Tipografía en tiempo de ejecución Pila de fuentes, tamaño, altura de línea y grosores.

- Asignación de estilos de texto Preajustes asignados a tokens globales.

- Escala tipográfica de diseño Tabla del lenguaje de diseño; Subtitle1 difiere del par en tiempo de ejecución.

- Espaciado en tiempo de ejecución Nombres de tokens horizontales y verticales.

- Pautas de diseño Espaciado global y consideraciones adaptables.

- Tokens de radio Valores de forma disponibles en tiempo de ejecución.

- Duraciones del movimiento Valores de duración en tiempo de ejecución.

- Asignación semántica del tema claro Asignaciones de alias neutros y de marca.

- Asignación semántica del tema oscuro Asignaciones de alias del tema oscuro.

- Escalas de marca Valores reales de marca para la web y Teams.

- Construcción del tema web webLightTheme usa brandWeb.

- Uso de Field Etiquetas, validación y pautas de composición.

- Field API Espacios y contrato de accesibilidad para elementos secundarios.

- Uso de TabList Pautas de diseño y selección de categorías.

- TabList API Opciones de selección controlada y foco.

- Uso de Dialog Pautas para la ubicación de formularios y la interacción modal.

- Dialog de API Visibilidad controlada y tipos de modal.

## Recursos relacionados con el sistema de diseño Microsoft Fluent 2

### Monday Vibe

Crea un tablero de entregas funcional con tokens de Vibe 4 y controles de React. Distingue los colores semánticos de acción de las etiquetas del tablero, explora los pasos de espaciado con nombre y las abreviaturas tipográficas, y conoce la configuración explícita de CSS que necesita una aplicación nueva.

### Morningstar sistema de diseño

Crea una interfaz de investigación donde los números se alineen, los colores de los activos mantengan su significado y la tipografía siga siendo legible. Explora las constantes de MDS, la adopción de Vue 3, la transición a Morningstar Intrinsic y un cuaderno de asignaciones ficticio que se puede ordenar.

### Mozilla Protocol

Una guía editorial de Protocol con pilas tipográficas, escalas de espaciado y contenedores reales, una página de lectura adaptable con ilustraciones y un ejemplo completo de formulario nativo de CSS.


- [Referencia de implementación](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-components/README.md)
- [Fuente](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/alias/lightColor.ts)
- [Fuente](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/brandColors.ts)
- [Fuente](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/spacings.ts)
- [Fuente](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/fonts.ts)
- [Fuente](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/borderRadius.ts)
- [Fuente](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/durations.ts)
- [Fuente](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/alias/darkColor.ts)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-components/README.md)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/docs/architecture/design-tokens.md)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/alias/lightColor.ts)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/alias/darkColor.ts)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/fonts.ts)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/typographyStyles.ts)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/spacings.ts)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/borderRadius.ts)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-tabs/library/src/components/TabList/TabList.types.ts)
- [Consulta la especificación complementaria](https://fluent2.microsoft.design/components/web/react/core/tablist/usage)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-field/library/src/components/Field/Field.types.ts)
- [Consulta la especificación complementaria](https://fluent2.microsoft.design/components/web/react/core/field/usage)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-dialog/library/src/components/Dialog/Dialog.types.ts)
- [Consulta la especificación complementaria](https://fluent2.microsoft.design/components/web/react/core/dialog/usage)
- [Consulta la especificación complementaria](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/durations.ts)
- [Referencia de código](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-components/README.md)
- [Referencia de código](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-field/library/src/components/Field/Field.types.ts)
- [Referencia de código](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-tabs/library/src/components/TabList/TabList.types.ts)
- [Punto de entrada del paquete React v9 Proveedor e instalación del conjunto.](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-components/README.md)
- [Arquitectura de temas Uso de tokens semánticos y temas disponibles.](https://github.com/microsoft/fluentui/blob/master/docs/architecture/design-tokens.md)
- [Tipografía en tiempo de ejecución Pila de fuentes, tamaño, altura de línea y grosores.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/fonts.ts)
- [Asignación de estilos de texto Preajustes asignados a tokens globales.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/typographyStyles.ts)
- [Escala tipográfica de diseño Tabla del lenguaje de diseño; Subtitle1 difiere del par en tiempo de ejecución.](https://fluent2.microsoft.design/typography)
- [Espaciado en tiempo de ejecución Nombres de tokens horizontales y verticales.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/spacings.ts)
- [Pautas de diseño Espaciado global y consideraciones adaptables.](https://fluent2.microsoft.design/layout)
- [Tokens de radio Valores de forma disponibles en tiempo de ejecución.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/borderRadius.ts)
- [Duraciones del movimiento Valores de duración en tiempo de ejecución.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/durations.ts)
- [Asignación semántica del tema claro Asignaciones de alias neutros y de marca.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/alias/lightColor.ts)
- [Asignación semántica del tema oscuro Asignaciones de alias del tema oscuro.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/alias/darkColor.ts)
- [Escalas de marca Valores reales de marca para la web y Teams.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/brandColors.ts)
- [Construcción del tema web webLightTheme usa brandWeb.](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/themes/web/lightTheme.ts)
- [Uso de Field Etiquetas, validación y pautas de composición.](https://fluent2.microsoft.design/components/web/react/core/field/usage)
- [Field API Espacios y contrato de accesibilidad para elementos secundarios.](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-field/library/src/components/Field/Field.types.ts)
- [Uso de TabList Pautas de diseño y selección de categorías.](https://fluent2.microsoft.design/components/web/react/core/tablist/usage)
- [TabList API Opciones de selección controlada y foco.](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-tabs/library/src/components/TabList/TabList.types.ts)
- [Uso de Dialog Pautas para la ubicación de formularios y la interacción modal.](https://fluent2.microsoft.design/components/web/react/core/dialog/usage)
- [Dialog de API Visibilidad controlada y tipos de modal.](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-dialog/library/src/components/Dialog/Dialog.types.ts)
