Notion UI — Sistema de Diseño
Interfaz minimalista tipo workspace: mucho espacio en blanco, tipografía del sistema, esquinas redondeadas suaves (6–20px), bordes casi imperceptibles (opacidad 5–11%) en lugar de líneas duras, y sombras sutiles y difusas compuestas en capas en vez de una sola sombra dura.
Regla de oro #1 — NO crear controles sueltos. Prohibido escribir un
<button>,<input>o<textarea>con clases ad-hoc sueltas en una pantalla. Todo botón pasa por un único componenteButton(con propsvariant/size); todo campo de texto pasa por un único componenteInput(yTextareasi aplica). Antes de maquetar cualquier control: (1) revisa si el proyecto ya tieneButton/Inputcompartidos (busca enshared/components/ui/o equivalente); (2) si existen, reutilízalos —a lo sumo añades una variante nueva a su API, nunca un elemento nativo suelto; (3) si de verdad no existen, créalos primero siguiendo la anatomía de la sección 6 y 7 de este documento, y luego constrúyelos sobre ese componente. Un<button className="...">a pelo en una pantalla es un error de revisión.Regla de oro #2 — nada de adornos ruidosos.
text-transform: uppercaseyletter-spacing(tracking-*) no se usan en esta UI — ni en botones, ni en labels, ni en badges. Los títulos y etiquetas se diferencian por tamaño y peso de fuente, nunca por mayúsculas ni tracking. Las sombras son SIEMPRE las de la sección 5 (contacto + ambiental + hairline de 1px) — nunca unshadow-lg/shadow-xlgenérico ni unbox-shadowinventado con blur grande y un solo color.Regla de oro #3 — color y tipografía son tokens, no valores fijos. El color de acento (
--color-accent) es el color de marca/primary del proyecto donde se aplique este sistema — en esta guía se usa#ED254Ecomo valor de ejemplo (el primary de to-do.eduardoguette.com), pero cualquier proyecto lo sustituye por el suyo sin tocar el resto del sistema (radios, sombras, spacing). La tipografía (--font-family) por defecto es Inter, pero debe quedar intercambiable (ver sección 2) — nunca hardcodear el nombre de la fuente en cada componente, siempre a través del token.Regla de oro #4 — Tailwind, no valores absolutos. Ningún tamaño de fuente se escribe como valor absoluto (
text-[14px],font-size: 14px,style={{ fontSize }}). Siempre se usa la escala semántica de Tailwind:text-xs/text-sm/text-base/text-lg/text-xl/text-2xl/text-3xl, confont-normal/font-medium/font-semibold/font-boldpara el peso (ver mapeo en sección 2). Lo mismo aplica a color: ningún componente escribe un hex/rgba suelto (bg-[#ED254E],text-[rgba(...)],style={{ color }}) — todo color sale de los tokens de esta guía expuestos como utilidades de Tailwind (bg-accent,text-accent,border-border,bg-hover,bg-canvas,bg-sidebar, mapeadas en eltheme/@themedel proyecto a las variables de la sección 10). Si un color o tamaño que necesitas no está en la escala/tokens, amplías el theme de Tailwind — nunca lo escribes inline como valor arbitrario en un componente.
1. Filosofía de diseño
Interfaz minimalista tipo workspace: mucho espacio en blanco, tipografía del sistema (sin fuentes decorativas), esquinas redondeadas suaves (6–20px), bordes casi imperceptibles (opacidad 5–11%) en lugar de líneas duras, y sombras sutiles y difusas compuestas en capas en vez de una sola sombra dura.
2. Tipografía
-
Familia por defecto: Inter —
"Inter", ui-sans-serif, -apple-system, system-ui, "Segoe UI Variable Display", "Segoe UI", Helvetica, Arial, sans-serif. -
La familia es un token intercambiable, no un valor hardcodeado en cada componente. Patrón recomendado (así lo resuelve to-do.eduardoguette.com): una variable CSS
--font-sansdefinida en el theme + un atributodata-font-familyen<html>que la sobreescribe (html[data-font-family="inter"] { --font-sans: "Inter", system-ui, sans-serif; },html[data-font-family="mono"] { ... }, etc.), controlado por un provider/contexto de React que persiste la preferencia del usuario. Cualquier fuente del sistema (system-ui, -apple-system) sirve de fallback aceptable si Inter no está disponible. -
Nunca un tamaño absoluto (
text-[Npx],font-sizeinline) — siempre la escala de Tailwind, mapeada así (ver Regla de oro #4):Uso Tailwind px de referencia Título de página (H1) text-3xl font-bold~30px (si se necesita fidelidad exacta a 32px, redefine text-3xlen eltheme, no lo escribas suelto)Elementos de navegación y botones text-sm font-medium14px Texto de cuerpo text-sm font-normal/text-base font-normal14–16px Tooltips text-xs font-normal12px -
Nunca
text-transform: uppercaseniletter-spacing/tracking-*para crear jerarquía — solo tamaño y peso de la escala anterior (ver Regla de oro #2).
3. Paleta de colores
Light mode
| Token | Valor |
|---|---|
| Fondo principal (canvas) | #FFFFFF |
| Fondo sidebar/aside | #F9F8F7 |
| Borde derecho sidebar | #F0EFED (1px) |
| Texto principal | #2C2C2B |
| Borde de botones/tarjetas | rgba(28,19,1,0.11) |
| Hover (filas, sidebar, menú) | rgba(0,0,0,0.03) a rgba(42,28,0,0.07) |
| Acento (botón primario, links) | #ED254E (texto sobre acento: #FFF5F7) |
Dark mode
| Token | Valor |
|---|---|
| Fondo principal (canvas) | #191919 |
| Fondo sidebar/aside | #202020 |
| Fondo menús/dropdowns | #252525 |
| Texto principal | #F0EFED |
| Borde de botones | rgba(255,255,235,0.1) |
| Borde de menús/modales | #383836 |
| Hover (filas, sidebar, menú) | rgba(255,255,243,0.082) |
| Acento (botón primario, links) | #ED254E (idéntico al light mode) |
Nota: el color de acento no cambia entre temas, y es el ÚNICO valor de esta paleta que se sustituye por proyecto (ver Regla de oro #3). El resto de la paleta (grises, bordes, hovers) es fija — es lo que le da el aire “Notion” a la interfaz independientemente de la marca.
Estos valores se declaran una sola vez en el theme/@theme de Tailwind del
proyecto (ej. --color-accent, --color-canvas, --color-sidebar, --color-border,
--color-hover) y los componentes solo consumen las utilidades resultantes
(bg-accent, text-accent, bg-canvas, bg-sidebar, border-border, bg-hover,
text-primary). Ningún componente escribe bg-[#ED254E], text-[#2C2C2B] ni
style={{ backgroundColor: ... }} con un valor de esta tabla — eso rompe la Regla de
oro #4 y hace que el sistema deje de ser un token único y reemplazable.
4. Componentes de navegación
Barra lateral (aside/sidebar): ancho fijo ~260px, ítems con icono + texto, radio de
6px en hover, agrupado por secciones con encabezados pequeños en color muted (nunca
mayúscula forzada vía CSS — si el copy ya viene en mayúsculas está bien, pero no se
aplica uppercase como tratamiento), colapsable con una flecha.
Barra superior (topbar): breadcrumb con ícono de página + nombre + candado “Private”
- chevron; a la derecha botones tipo pill (Share, link, estrella, más opciones) con borde sutil que se rellena en hover.
Buscador modal (estilo Cmd+K): modal centrado flotante, border-radius: 20px, fondo
semitransparente con blur (rgba(32,32,32,0.9) dark / rgba(255,255,255,0.9) light),
lista de resultados a la izquierda con fila activa resaltada (radius: 12px), panel de
vista previa a la derecha, pills de filtro arriba, footer con atajos de teclado.
5. Sistema de sombras (elevation system)
Se componen tres capas de sombra en casi todo elemento flotante (menús, popovers,
modales): una sombra de contacto ceñida, una sombra ambiental grande y difusa, y una
línea de 1px que actúa como borde nítido. Esto evita que los elementos “floten sin peso”
y mantiene el borde definido incluso sobre fondos con blur. No se usan sombras de una
sola capa ni utilidades genéricas tipo shadow-lg/shadow-xl.
Menú desplegable — dark mode:
box-shadow:
rgb(56, 56, 54) 0px 0px 0px 1px,
rgba(25, 25, 25, 0.2) 0px 14px 28px -6px,
rgba(25, 25, 25, 0.118) 0px 2px 4px -1px;
border-radius: 10px;
background: rgb(37, 37, 37);
Menú desplegable — light mode:
box-shadow:
rgba(42, 28, 0, 0.07) 0px 0px 0px 1px,
rgba(25, 25, 25, 0.05) 0px 20px 24px 0px,
rgba(25, 25, 25, 0.027) 0px 5px 8px 0px;
background: rgb(255, 255, 255);
Modal / buscador (misma lógica, blur y offsets más grandes por mayor jerarquía):
/* dark */
box-shadow:
rgb(56,56,54) 0 0 0 1px,
rgba(25,25,25,0.64) 0 24px 48px 0,
rgba(25,25,25,0.56) 0 4px 12px 0;
/* light */
box-shadow:
rgba(42,28,0,0.07) 0 0 0 1px,
rgba(25,25,25,0.24) 0 24px 48px 0,
rgba(25,25,25,0.14) 0 4px 12px 0;
Regla general: a mayor elevación jerárquica, mayor blur y mayor offset vertical de la sombra ambiental — pero el hairline de 1px se mantiene constante en todos los niveles.
6. Componente Button — API y anatomía (OBLIGATORIO usar este componente, nunca <button> suelto)
Un único componente cubre las 3 formas visuales de botón vistas en Notion. Se
diferencian por variant/shape, no por clases sueltas por pantalla.
| Prop | Valores | Notas |
|---|---|---|
variant |
primary | secondary | ghost |
primary = fondo --color-accent, texto claro. secondary = fondo transparente + borde --border. ghost = sin fondo ni borde, solo texto + hover. |
size |
sm (28px alto) | md (32px alto, pill) |
Ver alturas en la sección de tokens. |
shape |
rect (radius 6px) | pill (radius 20px) | split (radius asimétrico) |
split es un botón dividido: acción principal + chevron, ver abajo. |
loading |
boolean | Reemplaza el contenido por un spinner, mantiene el tamaño. |
disabled |
boolean | Opacidad reducida, cursor: not-allowed, sin hover. |
Botón píldora (pill), ej. “View all”:
height: 32px;
padding: 6px 12px;
border-radius: 20px;
background: rgba(255,255,235,0.1); /* dark; en light: rgba(0,0,0,0.03) */
transition: background 0.1s ease-in-out;
font: 500 14px var(--font-sans);
Botón rectangular estándar (variant="secondary"), ej. “Share”:
border-radius: 6px;
border: 1px solid rgba(28,19,1,0.11); /* light */
border: 1px solid rgba(255,255,235,0.1); /* dark */
background: transparent;
Botón primario (variant="primary"):
border-radius: 6px;
border: none;
background: var(--color-accent); /* #ED254E por defecto */
color: #FFF5F7;
Botón dividido (shape="split"), ej. “New”: dos div contiguos dentro de un único
contenedor. El segmento izquierdo (acción principal) usa border-radius: 6px 0 0 6px;
el derecho (chevron) usa border-radius: 0 6px 6px 0, separados por una línea vertical
sutil. Ambos: height: 28px, padding: 0 8px, fondo var(--color-accent). Esta técnica
de radios asimétricos hace que dos botones se vean como uno solo.
Si el proyecto no tiene un componente Button: créalo con esta API antes de usar
botones en cualquier pantalla. No se aceptan <button> con className inline
reinventando estos estilos pantalla por pantalla.
7. Componente Input (y Textarea) — API y estado de foco
Un input casi nunca tiene borde propio — el borde vive en un div contenedor que
envuelve al <input>/<textarea>. Input y Textarea comparten la misma anatomía de
borde/foco; Textarea solo cambia height/resize.
| Prop | Valores | Notas |
|---|---|---|
label |
string | Encima del campo, 14px/500. |
error |
string | Debajo del campo, color de error, sin mover el layout del input. |
startAdornment / endAdornment |
ReactNode | Icono o texto dentro del wrapper, no del <input>. |
En reposo:
border-radius: 6px;
padding: 3px 6px;
background: rgba(252,252,252,0.03); /* dark; en light: #FFFFFF con border rgba(28,19,1,0.11) */
height: ~28px; /* Textarea: auto, min 2 líneas */
Al enfocarse (:focus-within del wrapper, no del input), se añade un doble
box-shadow (inset + outset) del color de acento, simulando un anillo de foco de 2px
sin alterar el layout:
box-shadow:
var(--color-accent) 0px 0px 0px 1px inset,
var(--color-accent) 0px 0px 0px 1px;
border-radius: 6px;
Usar box-shadow en vez de border/outline permite animar el foco sin mover ni
redimensionar el elemento. Si el proyecto no tiene Input/Textarea compartidos,
créalos con esta anatomía antes de maquetar formularios.
8. Tooltips
Los tooltips son “invertidos”: usan siempre la misma paleta oscura, sin importar si la app está en light o dark mode.
background: rgb(44, 44, 43);
color: rgb(240, 239, 237);
padding: 5px 8px;
border-radius: 6px;
font-size: 12px;
white-space: nowrap;
box-shadow:
rgba(0, 0, 0, 0.08) 0px 4px 12px -2px,
rgba(255, 255, 255, 0.05) 0px 0px 0px 1px inset;
Comportamiento: altura fija de 27px independientemente del largo del texto, sin flecha/caret apuntando al elemento (solo un pequeño offset de posición), aparece tras un breve delay de hover y se desvanece con una transición suave.
9. Anidamiento (árbol del sidebar / páginas dentro de páginas)
Cada fila del árbol mide height: 30px sin importar el nivel de profundidad. Lo que
cambia es la posición horizontal del ícono/bullet y del texto, que se desplazan
exactamente 8px por cada nivel — y no hay límite de profundidad: una página puede
anidar otra página, que anida otra, indefinidamente (patrón “wiki”).
Nivel 0 (página raíz) → texto en left: 46px
Nivel 1 (subpágina) → texto en left: 54px
Nivel 2 (base de datos anidada) → texto en left: 62px
Nivel 3 (vistas: Table/Board/...) → texto en left: 70px
Las páginas usan un ícono SVG de 12px; las vistas dentro de una base de datos no llevan
ícono sino un bullet “•” más pequeño, comunicando jerárquicamente “esto es una vista, no
una página”. El hover de cualquier fila usa el mismo color y radio que los demás
componentes interactivos (rgba(255,255,243,0.082) dark / rgba(0,0,0,0.03) light,
border-radius: 6px).
10. Tokens de diseño (resumen para reutilizar)
:root {
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 12px;
--radius-xl: 20px;
/* Reemplazar por el primary del proyecto — este es solo el valor de ejemplo */
--color-accent: #ED254E;
--color-focus-ring: var(--color-accent);
/* Reemplazable — ver sección 2. Inter es la preferencia por defecto. */
--font-family: "Inter", ui-sans-serif, -apple-system, system-ui, "Segoe UI", Arial, sans-serif;
--h-button-sm: 28px;
--h-button-pill: 32px;
--h-input: 28px;
--h-row-sidebar: 30px;
--h-tooltip: 27px;
--indent-per-level: 8px;
--icon-size: 12px;
--tooltip-bg: rgb(44, 44, 43);
--tooltip-color: rgb(240, 239, 237);
}
[data-theme="light"] {
--bg-canvas: #FFFFFF;
--bg-sidebar: #F9F8F7;
--text-primary: #2C2C2B;
--border: rgba(28,19,1,0.11);
--hover: rgba(0,0,0,0.03);
--shadow-menu: rgba(42,28,0,0.07) 0 0 0 1px, rgba(25,25,25,0.05) 0 20px 24px 0, rgba(25,25,25,0.027) 0 5px 8px 0;
--shadow-modal: rgba(42,28,0,0.07) 0 0 0 1px, rgba(25,25,25,0.24) 0 24px 48px 0, rgba(25,25,25,0.14) 0 4px 12px 0;
}
[data-theme="dark"] {
--bg-canvas: #191919;
--bg-sidebar: #202020;
--bg-menu: #252525;
--text-primary: #F0EFED;
--border: rgba(255,255,235,0.1);
--border-menu: #383836;
--hover: rgba(255,255,243,0.082);
--shadow-menu: rgb(56,56,54) 0 0 0 1px, rgba(25,25,25,0.2) 0 14px 28px -6px, rgba(25,25,25,0.118) 0 2px 4px -1px;
--shadow-modal: rgb(56,56,54) 0 0 0 1px, rgba(25,25,25,0.64) 0 24px 48px 0, rgba(25,25,25,0.56) 0 4px 12px 0;
}
11. Checklist antes de dar por terminada una pantalla
- ¿Todo botón usa el componente
Button(variant/size/shape), sin<button>sueltos? - ¿Todo campo de texto usa
Input/Textarea, sin<input>/<textarea>sueltos? - ¿Cero
text-transform: uppercasey ceroletter-spacing/tracking-*en la UI? - ¿Las únicas sombras presentes son las de 3 capas de la sección 5 (o ninguna)?
- ¿El color de acento sale de
--color-accent(el primary del proyecto), no un azul/otro color hardcodeado? - ¿La tipografía sale de
--font-family, no un nombre de fuente hardcodeado por componente? - ¿Todo tamaño de fuente usa la escala de Tailwind (
text-xs/sm/base/lg/xl/2xl/3xl), sintext-[Npx]nifont-sizeinline? - ¿Todo color sale de una utilidad de Tailwind mapeada a los tokens (
bg-accent,bg-canvas,border-border…), sinbg-[#hex]/text-[rgba(...)]sueltos?
Adaptado de una guía basada en estilos computados reales (getComputedStyle) de una instancia de Notion (julio 2026), con el color de acento y la tipografía convertidos en tokens reemplazables por proyecto.