Conversation list
The reader's past conversations, grouped by date, each with a menu to rename, pin, archive or delete it.
-
<aparte-conversation-list>
On this page
<!-- It stores nothing: you assign the conversations, it renders the rows and tells you what was picked. -->
<aparte-conversation-list active-id="c1" style="max-width: 20rem"></aparte-conversation-list>
<script>
const day = 864e5;
document.querySelector('aparte-conversation-list').conversations = [
{ id: 'c0', title: 'Release checklist', updatedAt: Date.now() - 3 * day, pinnedAt: Date.now() },
{ id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },
{ id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - day },
{ id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },
{ id: 'c4', title: 'The first release', updatedAt: Date.now() - 20 * day },
];
</script><aparte-conversation-list> draws the rows; it is not a store. You assign the conversations
property and set active-id, it renders them and tells you what the reader picked. As soon as an
item carries updatedAt, the rows are grouped by date: Pinned, Today, Yesterday, Previous 7 days,
Previous 30 days, then one heading per month. flat keeps your order with no headings.
Each row is two buttons: its title, which selects it, and a ⋯ that opens its menu.
Variants
Section titled “Variants”The row’s menu
Section titled “The row’s menu”Press ⋯ on a row above: rename, pin or unpin, archive or unarchive, delete. Every item fires an
event and stops. Rename turns the title into a field (Enter or leaving it commits, Escape cancels); delete asks first,
inside the menu, because it is the one action you cannot undo.
While it loads
Section titled “While it loads”<!-- loading until your store answers: a list that is not there yet is not an empty list. -->
<aparte-conversation-list loading style="max-width: 20rem"></aparte-conversation-list>loading shows skeleton rows until your store answers: a list that is not there yet is not an
empty list. An empty list draws nothing.
<aparte-conversation-list flat=""></aparte-conversation-list>el.addEventListener('aparte-conversation-select', (e) => use(e.detail));<aparte-conversation-list flat=""></aparte-conversation-list>The aparte-* tags are typed JSX intrinsics as soon as you import from @aparte/react. A presence attribute takes '', never true — React stringifies it, and flat={false} would render flat="false", which hasAttribute reads as on. Events reach you by ref, typed through the DOM.
<template>
<aparte-conversation-list flat=""
@aparte-conversation-select="(e) => use(e.detail)"
></aparte-conversation-list>
</template>Declared through Vue’s GlobalComponents, so vue-tsc checks the tag in any template. A presence attribute takes '' to set and null to remove, never false.
<aparte-conversation-list flat=""
on:aparte-conversation-select={(e) => use(e.detail)}
></aparte-conversation-list>Declared through SvelteHTMLElements, so svelte-check covers the attributes and the on: handlers. A presence attribute takes '', never false.
import { AparteConversationListDirective } from '@aparte/angular';<aparte-conversation-list [flat]="true"
(conversationSelect)="use($event)"></aparte-conversation-list>A standalone directive whose selector IS the tag, so the real element sits in the template — @if, @for and content projection all reach it — and no CUSTOM_ELEMENTS_SCHEMA is needed.
Installation and the framework-specific traps: React · Vue · Svelte · Angular.
The events carry the conversation’s id (and the new title, for a rename); the array stays as it
was until you assign it again. To have the list carry out the menu’s actions itself, register a
conversation manager with setConversationManager() and set manage: renaming, pinning,
archiving and deleting then happen on that manager, and preventDefault() on the event still
takes the gesture back. manage does not select: a click sends aparte-conversation-select, which
a chat bound to the manager answers by opening that conversation.
The rows come from the array, so the element takes no children: put the new-chat button and the search field around it, in the sidebar.
<aparte-conversation-list> · class AparteConversationList
Attributes
| Attribute | Description |
|---|---|
active-id | The id of the conversation to render as selected. |
flat | Render the rows flat, in host order, with no date headings. |
loading | The list is on its way: six skeleton rows stand where the rows will be, the element is aria-busy and a visually hidden line names the wait. A list that is not there yet is not an empty list. Set it while your store answers, clear it when you assign conversations. |
manage | Let the list finish the gesture: rename, pin, unpin, archive, unarchive and delete are performed on the conversation manager registered with setConversationManager(), instead of leaving you six listeners to write. The event still fires first, and preventDefault() on it takes the gesture back. Off by default, and selecting stays yours either way. |
Properties
| Property | Type | Description |
|---|---|---|
loading | boolean | The list is on its way: rows of the kit’s skeleton stand where the rows will be, the element is aria-busy, and a visually hidden line names the wait. Reflected as the loading attribute, so a site sets it while its store answers and clears it when conversations is set. A list that is not there yet is not an empty list. |
manage | boolean | Let the list finish the gesture through the registered conversation manager. Reflected as the manage attribute. The property is not a nicety here: this is the attribute that authorises a WRITE, and Vue and both Sveltes stringify a bound false onto an element that has no property of that name — manage="false", which hasAttribute reads as on. So turning management off turned it on, and a delete the host meant to handle itself was also done by the list. presenceOn, like loading above it. |
conversations | AparteConversationListItem[] | Set the list of conversations to display. Triggers a re-render. |
Events
| Event | Type | Description |
|---|---|---|
aparte-conversation-select | CustomEvent<AparteConversationSelectDetail> | A row’s title was activated; the host loads that conversation. |
aparte-conversation-rename | CustomEvent<AparteConversationRenameDetail> | A rename was committed with a new, non-empty title. Cancelable: preventDefault() takes the gesture back from manage. |
aparte-conversation-pin | CustomEvent<AparteConversationPinDetail> | The pin item was chosen on an unpinned row. Cancelable: preventDefault() takes the gesture back from manage. |
aparte-conversation-unpin | CustomEvent<AparteConversationPinDetail> | The same item on a pinned row; same detail shape, opposite intent. Cancelable, like the rest. |
aparte-conversation-archive | CustomEvent<AparteConversationArchiveDetail> | The archive item was chosen on a live conversation. Cancelable: preventDefault() takes the gesture back from manage. |
aparte-conversation-unarchive | CustomEvent<AparteConversationArchiveDetail> | The same item on an already-archived one; same detail shape, opposite intent. Cancelable, like the rest. |
aparte-conversation-delete | CustomEvent<AparteConversationDeleteDetail> | The delete was confirmed. Cancelable: preventDefault() takes the gesture back from manage. |
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-conv-list-gap | var(--aparte-space-1) | Vertical gap between rows, and between a group’s heading and its rows. |
--aparte-conv-item-padding | var(--aparte-space-4) var(--aparte-space-5) | Padding of a row’s title button. |
--aparte-conv-item-gap | var(--aparte-space-3) | Gap between a row’s title and its ⋯ button. |
--aparte-conv-item-radius | var(--aparte-radius-md) | Corner radius of a row. |
--aparte-conv-item-font-size | var(--aparte-font-size-md) | Font size of a row’s title. |
--aparte-conv-item-color | var(--aparte-text) | Title colour of an inactive row. |
--aparte-conv-item-bg-hover | var(--aparte-surface-3) | Row background on hover. |
--aparte-conv-item-bg-active | var(--aparte-surface-3) | Background of the row matching active-id. |
--aparte-conv-item-color-active | var(--aparte-text) | Title colour of the active row. |
--aparte-conv-item-font-weight-active | var(--aparte-font-weight-medium) | Title weight of the active row. |
--aparte-conv-action-btn-size | var(--aparte-btn-size-sm) | Square size of the ⋯ button (the recipe’s small step). Under (pointer: coarse) the stylesheet redeclares it as the touch target size on the button itself, so a value set on the element does not reach it there; the button also stays visible instead of appearing on hover. |
Accessibility
Section titled “Accessibility”- The list is a navigation landmark named from the
conversationListlocale key (“Conversations”), unless you give it anaria-labeloraria-labelledbyof your own. - Each date group is a group named by its heading; the selected row is marked
aria-current. - The
⋯button announces a menu and whether it is open; the menu’s items are menu items. - While loading, the list is
aria-busyand a status line says the conversations are loading.
Related
Section titled “Related”- Sidebar — the column that holds the list, and filters it.
- Conversations guide — saving and restoring conversations.