Skip to main content

SelectedItem

Chip with a cross, for a value the user has picked and can take back. It is what a filter draws under its input for every active condition, and what DatePicker becomes once a date is chosen.

SelectedItemSelectedItem

Use this when / not when​

  • Use for a choice the user made and can undo — a filter term, a selected author, a chosen date — where removing it is a first-class action.
  • Not as a label on data the user did not choose; Tag is the room tag, smaller and without the removal semantics.
  • Not as a toggle in a group of options — TabItem is the selectable pill, and Checkbox the honest form control.
  • Not for one-off text with a colour; Badge is that.

Import​

import { SelectedItem } from "@onlyoffice/apps-ui-kit/components/selected-item";

Also exported from the root barrel @onlyoffice/apps-ui-kit.

Needs ThemeProvider from @onlyoffice/apps-ui-kit/providers/theme; without it the chip has no background and the disabled colours resolve to nothing.

Stories​

Default​

The chip as a filter shows a picked value: click the cross to see onClose in the Actions panel, click the label for onClick, and change any other prop live in the Controls panel below.

DefaultDefault

Disabled State​

For a value the user may see but not take back: the label and the cross grey out and neither handler fires (isDisabled).

Disabled StateDisabled State

Block Display​

For a list of picked values stacked one per row: the chip fills the width of its container and pushes the cross to the far end (isInline={false}).

Block DisplayBlock Display

All Variants​

How the modes sit together in a filter bar:

  • Inline enabled and Another item — inline chips wrapping in a row
  • Inline disabled — the same chip with its label and cross greyed out (isDisabled)
  • Block display item — a chip that fills the row (isInline={false})
All VariantsAll Variants

With Icon​

For a value that is easier to recognise by its kind: a glyph sits before the label (icon, here an SVG component; an SVG URL works too).

With IconWith Icon

Active State​

For the chip the user is working with right now: the background tints and the label and icon take the accent colour (isActive).

Active StateActive State

Without Cross​

For a value the user can pick but not remove from the chip itself: the cross is left out and only a click on the chip is reported (hideCross).

Without CrossWithout Cross

Truncated Label​

For values longer than a chip can hold: the label is cut off with an ellipsis; rest the pointer on the chip to read the full text in the tooltip (title). The tooltip is the kit's shared one, so the app must mount RootTooltip once, as this story does.

Truncated LabelTruncated Label

Css Customization​

Every overridable variable set on one wrapper -- the variables are listed under CSS variables on this page. The example has three chips: Custom item for the colours and sizes (hover it for the hover background), Disabled for --selected-item-disabled-text (isDisabled), and Active for the two active variables (isActive).

Css CustomizationCss Customization

Minimal example​

onClose is handed the propKey you gave the chip, so one handler serves a whole list.

import { useState } from "react";
import { SelectedItem } from "@onlyoffice/apps-ui-kit/components/selected-item";

export function ActiveFilters() {
const [terms, setTerms] = useState(["Reports", "2026", "PDF"]);

return (
<div style={{ display: "flex", flexWrap: "wrap" }}>
{terms.map((term) => (
<SelectedItem
key={term}
label={term}
propKey={term}
onClose={(key) => setTerms((t) => t.filter((x) => x !== key))}
/>
))}
</div>
);
}

Props​

PropertyTypeDescription
labelReactNodeText of the chip. A falsy label renders nothing at all.
onClose(propKey: string | number, label: React.ReactNode, group?: string, e?: React.MouseEvent) => voidCalled when the cross is clicked, with propKey, label, group ("" when it was not set) and the event.
propKeynumber | stringIdentifier handed back to onClose and onClick. It is not used for anything else.
className?stringApplied to the outermost element.
classNameCloseButton?stringApplied to the cross button, in addition to the class the component needs there itself.
clickable?booleanIgnored. Nothing reads this prop; passing onClick is what makes the chip clickable.
dataTestId?stringdata-testid of the outermost element.
forwardedRef?RefObject<HTMLDivElement | null>Ref to the outermost element.
group?stringSecond identifier handed back to both handlers, for chips that belong to several filters.
hideCross?booleanWhether the cross is left out. onClose then has nothing to fire it.
icon?FC<SVGProps<SVGSVGElement>> | stringGlyph before the label: an SVG URL, or a component rendered with no props.
id?stringApplied to the outermost element.
isActive?booleanWhether the chip is drawn in its selected colours.
isDisabled?booleanWhether the chip is inert. Both handlers stop firing and the label and the cross grey out. Default: false.
isInline?booleanWhether the chip shrinks to its content. Without it the chip fills the width of its container. Default: true.
onClick?(propKey: string | number, label: React.ReactNode, group?: string, e?: React.MouseEvent<HTMLElement>) => voidCalled when the chip is clicked, with propKey, label, group and the event. A click on the cross reaches it too, after onClose.
style?CSSPropertiesIgnored. Nothing reads this prop; style the chip through className or the custom properties.
title?stringText of the kit's shared tooltip for a truncated label; it shows only once RootTooltip is mounted.

Recipes​

Disabled / read-only​

isDisabled greys the label and stops both handlers. The cross is still drawn — use hideCross to take it away for a chip that cannot be removed at all.

import { SelectedItem } from "@onlyoffice/apps-ui-kit/components/selected-item";

export function FixedScope({ scope }: { scope: string }) {
return (
<SelectedItem
label={scope}
propKey="scope"
title="Set by your administrator"
isDisabled
hideCross
onClose={() => {}}
/>
);
}

A chip that is also a button​

onClick fires for a click on the chip, so the same chip can open the filter it stands for and remove it. A click on the cross reaches onClick too, after onClose (see below), so the handler here only toggles state the removal makes irrelevant.

import { useState } from "react";
import { SelectedItem } from "@onlyoffice/apps-ui-kit/components/selected-item";

export function AuthorFilter() {
const [author, setAuthor] = useState<string | null>("Anna Ivanova");
const [editing, setEditing] = useState(false);

if (!author) return null;

return (
<div>
<SelectedItem
label={author}
propKey="author"
group="people"
isActive={editing}
onClick={() => setEditing((v) => !v)}
onClose={() => setAuthor(null)}
/>
{editing ? <p>Pick another author</p> : null}
</div>
);
}

Behaviour the types don't state​

  • A falsy label renders nothing. The component returns null before anything else, so a chip whose label has not loaded yet disappears rather than showing an empty box.
  • onClose and onClick are not given the DOM event first. Both start with propKey and label; the event is the fourth argument and onClose receives "" for group when you did not set one.
  • A click on the cross fires onClose and then onClick. The chip skips onClick only when the clicked element itself carries selected-tag-removed, the class the component puts on the cross's own box — but that box is filled by the cross's SVG, so a real click lands on the <path> and bubbles up to the chip. classNameCloseButton is added beside that class, never instead of it. Where a click on the chip must not follow a removal, have onClick ignore a key onClose has just removed.
  • The chip brings its own spacing: margin-inline-end: 4px and margin-bottom: 4px. In a flex row with a gap those add up; set --selected-item-margin-inline and --selected-item-margin-bottom to 0 if the gap should be the only spacing.
  • isInline defaults to true and is what keeps the chip narrow. Passing isInline={false} makes it width: 100%, which is the block form used in a side panel.
  • The label is capped at 23 characters (max-width: 23ch) and truncated with an ellipsis. Pass title so the full text is available on hover.
  • title opens the kit's shared tooltip, not the browser's. Once mounted, the chip renders no title attribute; it is marked as a tooltip anchor, and the text shows only where the app has mounted RootTooltip once. Without it nothing appears on hover. Under NODE_ENV=test a native title attribute is rendered instead.
  • icon accepts either a URL or a component. A string is fetched and inlined by react-svg; a component is rendered with no props at all, so an icon that needs a size has to carry it itself.
  • style and clickable are declared and never read. Use className and the custom properties, and pass onClick to make the chip clickable.
  • The exported SelectedItemPure is the same component without React.memo.

CSS variables​

Set them on any ancestor.

VariableDefaultEffect
--selected-item-bgtheme tokenBackground of the chip.
--selected-item-bg-hovertheme tokenBackground while hovered; not applied while isDisabled or isActive.
--selected-item-active-bgtheme tokenBackground while isActive.
--selected-item-active-texttheme tokenLabel and icon colour while isActive.
--selected-item-disabled-texttheme tokenLabel colour while isDisabled.
--selected-item-radius3pxCorner radius.
--selected-item-padding6px 8pxInner padding.
--selected-item-height32pxHeight of the chip.
--selected-item-margin-inline4pxTrailing margin.
--selected-item-margin-bottom4pxBottom margin.
--selected-item-label-margin10pxGap between the label and the cross.

Accessibility​

  • The chip is a <div> with a click handler. It has no role, no tabIndex and no key handler, so onClick is unreachable from the keyboard.
  • The cross is an IconButton, which is also a <div> with no role, no name and no tabIndex. A keyboard-only user cannot remove a chip, and a screen reader is told nothing about what the cross does. Where removal has to be reachable, render your own Button beside the chip and pass hideCross.
  • title feeds the shared pointer tooltip and leaves no title attribute on the chip, so it is not a label either.
  • isDisabled is a colour and a guard in the handlers. No aria-disabled is set on the chip, so assistive technology is not told it is inert; only the cross carries aria-disabled, true while isDisabled and false otherwise.

Test ids​

Elementdata-testid
The chipselected-item, or dataTestId
The crossicon-button

The cross's id comes from IconButton and cannot be set from here; query it by the selected-tag-removed class instead when there are several chips.

  • Tag — the room tag, for labels the user did not pick.
  • DatePicker — draws its chosen date as one of these.
  • IconButton — the cross.