Raft 设计系统

Components

The two layers of raft-ui (core parts for any app, collaboration parts that carry Raft's product concepts), how to choose a component, and what is built from existing parts.

raft-ui's components come in two layers. Core holds the parts any app needs: buttons, fields, dialogs, tabs, tables, avatars. Collaboration holds the parts that carry Raft's own concepts: messages, threads, tasks, agents, Activity and notifications, search and files. This page says what each layer is for, which component to choose when several look right, and what to assemble from existing parts instead of designing anew. Each component's own doc says how to use it once chosen.

Two layers

Core

Core components are generic. They know nothing about messages, tasks or agents: their API speaks in values, items, slots and states, and they would work unchanged in an app that has none of Raft's concepts. Button, Field, Select, Dialog, Drawer, Tabs, Table, Markdown, Avatar and Status are core.

Core is grouped by what a part does:

CategoryWhat is in it
ActionsButtons
InputsForm controls and the parts around them: inputs, pickers, switches, labels and fields
NavigationMoving between places and sections: the rail, the sidebar, the phone tab bar, tabs, steps, segmented controls
OverlaysEverything that floats above the page: dialogs, drawers, menus, popovers, tooltips, the lightbox
FeedbackTelling people about a result, a problem, a wait or an empty view
LayoutThe frame of a screen and its regions: the app shell, panels, cards, separators, resizable columns
Data displayShowing content and facts: text, Markdown, code, tables, description lists, list rows, long virtualized lists, badges, status pixels
IdentityWhat stands for someone or something: avatars, icon tiles, the conversation icons, the logo

Collaboration

Collaboration components embody Raft's collaboration domain. Each one carries a product concept and the rules that come with it: what a message shows under its body, how a thread is summarized under the message it started, which statuses a task moves through and in what order, what an agent's request for approval records once it is handled. They are built from core parts.

Collaboration is grouped by concept:

CategoryWhat is in it
MessagesOne message and its parts (attachments, embeds, replies, translation), event lines, the message list, the composer, and the conversation and thread panels
TasksTask cards, the board and list views, the parts every task view shares, and the timeline that shows a task's history
AgentsThe profile of a person or an agent, and the card an agent attaches to a message for a person to confirm
InboxActivity, its rows, and the notification center
SearchThe search trigger and the rows of search results
FilesThe files shared in a place, and the icon that shows a file's type

Why the split

  • Core can be reused anywhere. Every Raft product (Raft Chat today; Tasks, Calendar and the others next) and any internal tool can take a core part without taking Raft's data model with it. raft-ui keeps this true in code: a core component never imports a collaboration one, while collaboration components are built on core ones.
  • Each rule lives in one place. A rule about color, lines, shape or states belongs to a core part, and every product inherits it. A rule about a product concept (how many reply previews a thread shows, the order of the task statuses) belongs to the collaboration part that carries the concept, so changing the concept changes one component, not every screen that shows it.
  • It tells you where to look. When you build something about messages, tasks, agents, Activity or notifications, start in collaboration: the part you need, or most of it, probably exists, and it already follows the product's rules. When you build something generic, stay in core, and don't borrow a collaboration part because it looks right. An InboxItem is the row for something that happened elsewhere; a list of settings uses ListRow, even though both are rows with an icon and a title.

Which layer a new component belongs to

Ask one question: would the part make sense in an app with no messages, tasks, agents, Activity or notifications?

  • Yes: it is core. Name it for what it does (Timeline would be core if it did not know task statuses), give it no product props, and put it in the category of what it does.
  • No: its name, its props or its rules mention a Raft concept, so it is collaboration. Build it from core parts and put it in the category of its concept.

A core part that starts to need a product concept has become two parts: keep the generic one in core and build the product one on top of it in collaboration.

How to choose a component

Several components can look right for the same job. These tables pick one per situation, and each section starts with the question that decides it. Use them whenever you put a control, a message, a popup or a row on a screen. Products don't override these choices; a product-specific case goes on the product's page.

Picking something

Two questions decide a picker: when the choice takes effect, and how many values there are. A control that applies at once (Switch, a menu) must be safe to flip without a Save; a control saved with a form (Checkbox, RadioGroup, Select) can wait for the person to review it (Forms). Radio buttons show every answer, so they fit only a few; a select hides the values behind one field, so it fits more; past that people need to search.

You needUse
To act on one thingA menu (DropdownMenu); press and hold on a phone opens the same menu (ContextMenu)
Yes or no, applied at onceSwitch
Yes or no, applied on SaveCheckbox
One of 2 to 5 answers, submitted with a formRadioGroup
Another view of the same contentSegmentedControl
Sections of one page, each with its own contentTabs
One of up to 10 valuesSelect
One of many valuesA field people type into to filter the list, on a phone too (ComboboxSelect). When people only pick and never type, a Select: ComboboxSelect always brings up a phone's keyboard
A few of several valuesA multi-select: a box before each item, and the field shows the chosen values (Select with multiple)
A few of many values (people, agents, labels)A multi-select with search: chosen values become chips in the field, and typing continues after them (ComboboxSelect with multiple, or Combobox with its chips)
To filter a listA filter: a select or combobox opened from a PickerTriggerButton
To change one property of an item in placeThe value in a DescriptionList opens a picker

SegmentedControl and Tabs look alike, and the content decides between them: a segmented control shows the same content another way (Board or List), tabs switch to different content.

Telling people something

The scope of a message decides where it goes, and how long the situation lasts decides how long the message stays. The wider the scope, the further out it sits: an input's error under the input, a panel's problem under the panel header, a server's problem in the notification center (Feedback).

SituationUseStays
Something the person just did has a resultToastA few seconds
They need to know it before actingBanner, inside a dialog or formAlways
Something in one panel failed: an agent won't start, a setting of the conversation didn't saveA full-width notice under the panel header (PanelNotice, Panel)Until it is fixed or dismissed
The state of the whole server: a computer offline, a quota nearly full, an updateThe notification center (NotificationCenter), with an attention dot on the bellUntil it changes
Something happened elsewhere that the person should seeActivity (ActivityInbox); its entry point shows an attention dot or a countUntil it is read
An agent needs approvalAn action card in the conversation (MessageActionCard)Until it is handled
The person must decide nowDialogUntil they decide
An input is invalidThe input's error state (Field)Until it is fixed
What an icon meansTooltipWhile hovered
  • Never put anything in a toast that would confuse people if they missed it. A toast disappears on its own, so it can only confirm what the person already expects.

Overlays

The more an overlay asks of people, the more of the screen it takes. What only informs opens on hover; a small choice opens next to what was clicked; something people look at for a while slides in with the page still behind it; what they must finish before going on sits in the center over a scrim.

SituationUse
One sentence on hoverTooltip
A person or agent, on hoverA profile card (PreviewCard)
A list of actions, on click or press and holdDropdownMenu
One value, on clickSelect, with search when there are many values
Several values, on clickA multi-select, with search when there are many values. Choosing a value doesn't close the list
A little input, on clickPopover
Details and settings people look at for a whileDrawer
A large form filled in one passThe wide Dialog; see Forms
A short list opened from a top bar: Working now, the notifications, the server listPopover, on a phone too. On a phone the server list is anchored under the top bar (Navigation)
A short explanation, on a phoneA bottom sheet (Drawer)
A dialog with several fields, a long list or steps, on a phoneA sheet 90% of the screen tall; a decision, content that is only read, or a single input stays the centered dialog (Dialog)
A confirmationA Dialog at size="sm", 400px, the width of AlertDialog, with Cancel and the confirm button; see Feedback. Raft Chat builds its confirmations on Dialog, except two that use AlertDialog: discarding unsaved edits to an agent's profile, and opening a link that hands off to another app
Task detailsA centered dialog, 960px wide, full screen on a phone: a workspace overlay outside the four Dialog sizes (Tasks, Dialog)
  • Put a scrim behind every centered overlay: dialogs, confirmations, search, task details, large forms. How overlays move is in Motion.

Waiting

The more you know about a wait, the more specific its indicator. A known layout is held by a skeleton, so nothing jumps when the content arrives; a known percentage is a progress bar; a wait with neither is the spinner; and a wait a button started stays on the button, where the person is already looking.

SituationUse
The layout is knownSkeleton
The duration is unknown, and there is no layout to holdThe pixel spinner (Spinner)
The percentage is knownProgress
A button started the waitThe button's loading state (Button)
  • Don't use pink for any of these. They are passive, and pink means something waits for the person.
  • Raft Chat doesn't follow the skeleton rule in two places yet: the conversation timeline and the profile panel show the word "Loading…" while they wait.

Rows

A row follows what it stands for, not where it appears: a place looks the same in the sidebar and in a phone list of places, and each other kind of row carries what its subject needs (the attention dot of something to see, the status chip of a task). Rows of the same kind are also the same size across an app's tabs (The same kind, the same size).

WhereUse
The desktop sidebarThe sidebar row (Sidebar)
A phone list of places: Home's channels and direct messages, Members, the Settings listThe same sidebar row, 44px high on a phone (Sidebar)
Agents, people and apps on a detail or profile page, such as a computer's agentsListRow, in an inset group. The product draws this row with its own copy (AvatarListRow)
Activity and notificationsInboxItem
A message found by searchThe search result row (Search)
A taskTaskCard, on a board or in a list

Built from existing parts

Some things in the product have no design of their own: they are assembled from existing components. Build them as described here, without new colors, shapes or lines. Each new container, color or line is one more thing people have to learn and one more thing the system has to keep consistent across light and dark, phone and desktop; a composite inherits its parts' states, dark mode and accessibility for free.

If what you need isn't here and no component fits, follow the six questions in the README before adding anything.

Task details

A centered Dialog, 960px wide, a workspace overlay outside Dialog's four sizes: a bar with the place and the task number, the title and the description, one row of properties (Status, Assignee, Created by) in a DescriptionList, the History in a collapsed Timeline, then the task's thread and a Composer. Its size and its bar are in Tasks.

File viewer

Every file someone opens from a message, whether an image, a PDF, text, Markdown, a table, a web page, a video or audio, opens in the same viewer: a Lightbox with a LightboxToolbar on top and the file's own body below. Only the body changes with the type. People learn where a file's actions are once, and it holds for every file.

  • The toolbar is the same for every type: the type badge, the file name, then the actions in a fixed order (Comments, Copy, Download, then Close last). An action that doesn't apply to a file is left out; the rest keep their order.
  • On a phone the toolbar keeps the file name, one More menu and Close. Comments, Copy and Download move into the menu; when Download is the only action, it stays as its own button. The type badge and the gallery count drop out, because the name needs the room and the thumbnails already show the position.
  • Comments on a document the viewer renders sit in a margin beside it, level with their passages; on a narrow viewer, markers beside the passages open them in a bottom sheet (Comments).
  • Plain text sits in the CodeBlock frame, but its lines wrap. A file in the viewer is read top to bottom, and find in page has to reach every line. A snippet in a message still scrolls sideways.
  • Images keep the stage, previous and next, and the thumbnails below the toolbar.
lightbox/compositions/attachment-preview-shell

The structure of a file card (MessageAttachment) with the header of a message (MessageItem): MessageEmbed, MessageForwardedBundle, MessageQuotedPreview.

message-forwarded-bundle/compositions/forwarded-bundle
message-quoted-preview/compositions/quoted-preview

Diagrams and Markdown tables

The same container as a code block (CodeBlock): layer-code, a hairline border, radius-container.

Translation, read and saved markers

One more small chip in the row under the message (Saved), or a short line of Space Mono text: a translation under the original, and "Read" as one status line under the last message of a direct message (MessageTranslation). Don't add a color for them.

message-translation/compositions/translation-showcase

Toolbar for selected messages

While messages are selected, a toolbar takes the composer's place at the bottom of the conversation: Select All, Cancel, Forward, Copy link, and a More menu with Generate image and Copy MD. Select Message in the message menu starts the selection; each message gets a checkbox (MessageItem). It is a floating toolbar, 36px tall like the hover toolbar: its buttons are 28px, 4px inside it and 4px apart, and wear radius-chip (Shape).

message-item/compositions/selection-showcase

Resizable columns

The seam between two columns is the only line, and the drag handle sits on it (Resizable).

Tables

Prefer a DescriptionList or ListRow rows. When it has to be a table, such as usage or billing (Table):

  • Set the header in text-footnote, foreground-hint.
  • Divide rows with line-hairline.
  • Set numbers in Space Mono, right-aligned.

Picking a date

A column of dates inside the popup of a Select, with the dates in Space Mono.

A row of toggle buttons

A row of filters (ToggleGroup), each one selectable on its own.

toggle-group/compositions/formatting

Authorization (OAuth)

The structure of a Dialog, with the requested permissions listed in a DescriptionList.

Switching servers

  • The trigger is the WorkspaceButton on every surface: the server's avatar, its name and a chevron.
  • On desktop, the workspace button at the left of the web top bar opens the list as a menu under it, 256px wide (Popover). The strip of server avatars beside the button switches with one click.
  • On a phone, the workspace button on Home opens the same list as a popover anchored under the top bar, 8px from each side (Popover, Navigation).
  • Make each row the workspace tile, its name and its address in Space Mono (/launch). Mark the current server with a check and a bold name; show the unread count of the others as a pink count, or a gray count when the server is muted.
  • Give each row a drag handle, so people can reorder the list. The strip follows the same order.
  • End the list, after a separator, with Join Community (until the person has joined it), Switch or Create Server and Invite Human.

Every component

The component index lists every component raft-ui exports, grouped by layer and then by category, with its summary and status. Deprecated components keep their docs but are left out of the navigation. The same list, for agents, is in components.json and llms.txt.

  • Feedback: errors by scope.
  • Forms: which controls apply at once and which are saved.
  • Conversation: where most of the parts built from existing ones appear.
  • Authoring: the front matter that puts a component in its layer and category.

核心

操作

ButtonA button for an action, with or without a label; its color says what kind of action it is, and its size follows where it sits.TextLinkText that underlines on hover, for the quietest control on a screen, an action or a way somewhere; the bottom step of the button ladder.

输入

CheckboxA square box for a yes or no that is saved with a form, or an option attached to an action.ComboboxA searchable list of values built from parts, for filters with search and for picking a few of many values as chips.ComboboxSelect实验性A ready-made searchable select driven by an array of options, for one or a few of many values, with groups, remote search and creation.FieldThe label, help text and error around a form control, wired to it for assistive technology.InputA single-line text field at the control height, with a 1.5px ink border, for typing one value.InputGroupAn input with a fixed prefix, suffix, icon or button inside the same bordered box, such as a domain, a unit or a search icon.LabelA standalone form label in the field-label style, with optional markers for a required or optional value and a line of secondary text.PickerTriggerButtonThe compact 32px button that opens a Select or Combobox as a filter above a list, outlined until the filter is applied and filled once it is.RadioGroupA group of round options for choosing one of 2 to 5 answers that is submitted with a form, each with room for a sentence.SelectA field that opens a list of values and shows the one chosen, for picking one of up to 10 values or a few of several.SwitchA round on/off control for a setting that applies the moment it is flipped.TextareaA multi-line text field for forms, at least three lines high, with an optional character counter.ToggleGroupA compact row of toggle buttons in a gray track, each one on or off on its own, or one of them or none; a pressed button is a yellow sticker.

导航

AppRailThe leftmost desktop column, on the frame, under the web top bar — the product's destinations at the top, help and settings at the bottom.MobileNavThe phone tab bar — up to 5 destinations at the bottom of a top-level page, the selected one's icon in a yellow sticker.SegmentedControl2 to 4 segments in a gray track that switch how the same content is shown, in place; the selected segment is a yellow sticker.SidebarThe desktop column next to the rail that lists what the selected destination contains, with rows that mark where you are, what is unread and what mentions you.StepsWhere someone is in a short flow of 3 to 5 steps — finished steps in ink, the current step as a yellow sticker, the steps ahead outlined.TabsA row of tabs for the sections of one page, each with its own content; the selected tab is a yellow sticker, and the row scrolls when the tabs don't fit.WorkspaceButtonThe server switcher on the frame — the server's avatar, its name and a chevron, one button that opens the server list.

浮层

AlertDialogA small centered dialog that asks people to confirm an action that cannot be undone or that interrupts work, with a confirm button colored by the consequence.AnnouncementDialog实验性A dialog that tells people about something new in the product, with a small label, a title, an optional picture, a few lines of text and an action to try it.ContextMenuThe menu of actions on one thing that opens at the pointer on right-click, or on press and hold on a touch screen.DialogA centered overlay on a scrim for a decision or a few fields people must finish before they continue, with a wide size for a large form filled in one pass.DrawerA panel that slides in from the right for details and settings people look at for a while, and on a phone a bottom sheet for picking a place to go or reading a short explanation.DropdownMenuA list of actions on one thing, opened from a button, that can also hold a setting-style choice with a check mark on the current value.LightboxA full-screen viewer over the scrim for images and attachment previews, with previous and next, thumbnails and one button to close.PopoverA small panel that opens next to the element people click, for a little input such as a choice, a field and a button.PreviewCardThe read-only profile card that appears when people hover over or focus a mention or a name, showing a person's or an agent's avatar, name, status line and up to two small buttons.TooltipOne line that appears on hover or keyboard focus and gives something without a visible label its name, its consequence or its shortcut.

反馈

BannerA soft-tinted notice inside a dialog or form that people read before they act, and, as PanelNotice, a full-width strip under a panel header when something in that panel failed.EmptyStateWhat a view shows when it has no content — a gray icon, a title, one sentence on why it is empty and what to do, and at most one button.ProgressA bar that shows how far a measurable task has gone, such as an upload or usage against a limit, with a label and its value.SkeletonA gray placeholder in the shape of the content that is loading, for lists, cards and panels whose layout is known.SpinnerThe pixel spinner, four square pixels lit in turn, for a wait of unknown length with no layout to hold.ToastA dark floating notice that confirms the result of something the person just did, disappears after a few seconds, and can offer View or Undo.

布局

AppShellThe outermost layout of an app, with slots for the rail, the sidebar, the main area, a side panel and an overlay, that switches to a single column with the tab bar below 768px.CardA white container with a hairline that groups one subject on a page — a header with a title and description, content and a footer — or makes a selectable option inside a form.FormSectionOne part of a large form, with a small heading, a quiet status chip, a line on what the part is for, and its fields, used in the wide dialog and in drawer forms.PanelA column of the desktop main area with a 56px panel header and its own scrolling content; its header is also the top bar of a phone's pushed pages.ResizableColumns or rows whose split people drag, with the drag handle sitting on the seam between them.ScrollAreaA region with native scrolling and a thin drawn scrollbar that shows only while it scrolls, for lists and strips inside a fixed frame.SeparatorA 1px hairline between groups of content in one region, horizontal or vertical, with an optional word in the middle of a horizontal one.

数据展示

BadgeA small label — above all the pink unread count and the yellow @ marker, plus soft-tinted and hairline labels.CodeBlockSeveral lines of code or a log on a framed code surface, with an optional header for the file name and a copy action.CopyableCodeOne command people copy and run, on a code surface with a copy button that confirms with a check.DescriptionListA list of facts about one item — label and value pairs such as a task's Status, Assignee and Created by — where a value that can be changed opens a picker.Ellipsis实验性Text cut to one line or a few with CSS, which shows its whole text in a tooltip, or expands, only when it is actually cut.InlineCodeA name from code inside a sentence — a variable, a key, a file name — set in Space Mono on a sunken fill.KbdA keyboard key or shortcut shown as a hint, in Space Mono inside a hairline box — in menus, tooltips, the search trigger and inputs.ListRowThe row for the people and agents on a detail page, settings and shortcuts — a leading block, a title and a preview, the time and a badge.MarkdownThe styled elements a Markdown renderer maps its nodes to — headings, paragraphs, links, lists, quotes, tables, images and task checkboxes — for text people and agents write.StatusThe square status pixel — a presence light, an attention dot or a status color beside a word — and the status chips, agent tag and runtime chip built with it.TableRows and columns of data on native table elements, for the few places that need columns, such as usage, billing and permissions.TextPlain typography parts — Text.Heading, Text.Sans and Text.Mono — for text that no other component holds, in the system's two typefaces.VirtualListA scroll container for long lists whose rows have different heights, known only once they mount; it renders the rows in view and keeps what the person is reading still while sizes and rows change.

身份

AvatarThe square sticker that stands for one agent or one person — a pixel face for an agent, a photo or initials for a person — with an optional presence light.IconsThe four conversation icons raft-ui draws itself — Message, Chat, Direct Message and Thread; every other icon comes from Lucide.IconTileA neutral square with an icon, sized like an avatar, for a place or a thing without a face — a channel, a system notice, a file, an app, a computer.RaftLogoThe Raft mark, wordmark and lockup as inline SVGs, for brand pages such as sign-in, creating an account or a server, and loading screens.

协作

消息

Comment实验性Comments beside a document, Google Docs style — a margin of cards each level with the passage it is about, the card of one discussion, its comments, and the marker that replaces the margin on a narrow screen.ComposerThe message box at the bottom of a conversation, with the input, its tools, attachments, @ suggestions and the pink send button inside one ink border.ConversationPanelThe main-area panel for one conversation — a panel header, an optional row of tabs, the message list and the composer.MessageAttachmentFiles, images and video attached to a message — a file is a card with its name, size and an action; images and video show inline.MessageEmbedThe card that embedded message content is built on — a header, content and footer in a hairline card — used for link cards and as the base of forwarded bundles, action cards and reply summaries.MessageForwardedBundleA card under a message that holds one or more messages forwarded from another conversation, with their authors, times and attachments, folded when it runs long.MessageItemOne message in a conversation — avatar, name, time and body, with attachments, embedded cards and a footer row — used the same way for people and agents.MessageItemTranslationThe parts that show a message was translated — a short Space Mono status line under the body that switches between the translation and the original, the original kept below, and the translating and failed states.MessageListThe scrolling timeline of a conversation or thread, with the date lines, the notice at its start, the marker for limited history and the jump controls that float over it.MessageQuotedPreviewA two-line excerpt of the message a link points to, with its author, place and time, shown under the message that links to it.MessageRepliesThe thread under a message — a gray block with the reply count, a pink count of new replies and up to 3 previews — that opens the thread as one control.SystemMessageAn event line in a conversation — a date, a claim, a hand-off, a status change — set in Space Mono between hairlines, alone or collapsed into a group.ThreadPanelThe side panel for one thread — a header that names it, the original message, the replies and a composer, with no tabs.

任务

TaskBoardThe board view of tasks — one column per status, in status order, with cards that can be dragged to another column to change their status.TaskCardThe card that shows one task — its number, title and a status chip that changes the status — on a task board and in a task list.TaskDndThe drag-and-drop parts that let people move task cards between columns and around other cards, with the drop policy and the move left to the caller.TaskListThe list view of tasks — one collapsible group per status, in status order, each holding task cards.TasksThe parts every task view shares — the status badge and task chip that show where a task is, the status pixels, and the shell of a task panel.TimelineAn ordered list of what happened — a task's history in task-status colors and plain words, or an agent's activity log in presence colors and Space Mono.

Agent

MessageActionCardA card an agent attaches to its message with an action already filled in, for a person to confirm; afterwards it records who did it and when.ProfilePanelThe side panel for one person or agent — a panel header with their avatar and name, then sections of who they are, what they do and what you can do with them.

收件箱

ActivityInboxThe Activity panel — a list of inbox items with its views, filters and selection toolbar, where selecting an item opens it beside the list.InboxItemThe row for something that happened elsewhere and that the person should see — a mention, an approval, a hand-off — with an attention dot while it is unread and its actions in the row.NotificationCenterThe list that opens from the bell and holds the state of the whole server — a computer offline, a quota nearly full, an update — until it changes.

搜索

SearchThe search trigger in the web top bar, and the parts of search results — rows for places and people, message and thread hits, grouped by kind with the matched words marked.

文件

FilePreviewA small icon that shows a file's type — a document, an archive, code or a media thumbnail — with a chip that names the type.FilesPanelThe list of files shared in a place, one row per file with its type, name, size and time, which opens a preview and offers Jump to Message and Download.