PlChip
A compact token: a tag, a filter, a status, an entity plucked out of a list. It can carry a count, be pressed, be removed, or all three at once.
import { PlChip } from 'plass-ui';
<PlChip>design</PlChip>;
<PlChip selected onClick={toggle} count={12}>
open
</PlChip>;
<PlChip onDelete={remove}>infra</PlChip>;import 'package:plass_ui/plass_ui.dart';
const PlChip(child: Text('design'));
PlChip(selected: on, onPressed: toggle, count: const Text('12'), child: const Text('open'));
PlChip(onDeleted: remove, child: const Text('infra'));Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | 'solid' | 'glass' | 'ghost' | 'glass' | What the surface is made of. A chip is the thing being coloured, so its sheet takes the tint. glass by default: a row of gradient keys is a row in which nothing is the primary action |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | One step down the control ladder — a md chip is a sm control, 32px rather than 40px, because a chip is a token inside a row rather than a control the row lines up against |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | 'default' | 'compact' | 'default' | Padding only — never the height, never the type scale |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth. 0 means no shadow at all |
| startIcon | ReactNode | — | Content placed before the label — an icon, a status dot, an avatar |
| endIcon | ReactNode | — | Content after the label, before any count |
| count | ReactNode | — | A number set into the end of the chip, on its own small plate, so "Errors 12" reads as one token with a count rather than as two words |
| onClick | (event: MouseEvent) => void | — | Passing it turns the label into a real button. The shell stays a span: a button inside a button is invalid HTML that browsers un-nest on parse |
| onDelete | (event: MouseEvent) => void | — | Passing it is what makes the delete button appear — a second real button with its own tab stop, separate from the label |
| deleteLabel | string | 'Remove' | Accessible name of the delete button. Never drawn |
| selected | boolean | false | A filter that is on. It moves one step up the ladder its own variant already sits on rather than changing the colour family — a filter that is on is still the same filter |
| disabledshared | boolean | false | Unavailable. The light goes out, the same way it does everywhere else |
| children | ReactNode | — | The label |
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | PlassVariant | PlassVariant.glass | What the surface is made of. A chip is the thing being coloured, so its sheet takes the tint. glass by default: a row of gradient keys is a row in which nothing is the primary action |
| sizeshared | PlassSize | PlassSize.md | One step down the control ladder — a md chip is a sm control, 32px rather than 40px, because a chip is a token inside a row rather than a control the row lines up against |
| colorshared | PlassColor | PlassColor.primary | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | PlassDensity | PlassDensity.standard | Padding only — never the height, never the type scale |
| elevationshared | int | 0 | Drop shadow depth. 0 means no shadow at all |
| startIcon | Widget? | — | Content placed before the label — an icon, a status dot, an avatar |
| endIcon | Widget? | — | Content after the label, before any count |
| count | Widget? | — | A number set into the end of the chip, on its own small plate, so "Errors 12" reads as one token with a count rather than as two words |
| onPressed | VoidCallback? | — | Passing it turns the label into a real button. The shell stays a span: a button inside a button is invalid HTML that browsers un-nest on parse |
| onDeleted | VoidCallback? | — | Passing it is what makes the delete button appear — a second real button with its own tab stop, separate from the label |
| deleteLabel | String | 'Remove' | Accessible name of the delete button. Never drawn |
| selected | bool | false | A filter that is on. It moves one step up the ladder its own variant already sits on rather than changing the colour family — a filter that is on is still the same filter |
| disabledshared | bool | false | Unavailable. The light goes out, the same way it does everywhere else |
| child | Widget? | — | The label |
Every native <span> attribute passes straight through, onto the shell. color is excluded from the pass-through because it is a Plass prop here.
count is a Widget? rather than a number, because unlike a badge's it is never capped. The plate holds whatever it is given.
What the shared axes (variant size color density elevation) mean across the library is in prop conventions.
Examples
onClick and onDeleteonPressed and onDeleted
The shell is always a <span>. What changes is what is inside it: a plain run of content, or (when onClick is given) a real <button> wrapping that content, plus a second button for onDelete.
That is not indirection. A <button> inside a <button> is invalid HTML that browsers un-nest on parse, so keeping the shell a <span> is what lets "activate this chip" and "remove this chip" both be real, focusable buttons.
A chip that can be pressed and a chip that can be removed are two separate focus stops, and neither is inside the other's gesture recogniser. The reason is the same one the React build has for keeping the shell a <span>, arrived at from the other end: a tap that reached both would fire both.
A chip with no onPressed takes no focus stop and is announced as content rather than as a button. It is a tag, and a tag is not something you press.
variant
A chip is the thing being coloured, a tag names one particular thing, so unlike a PlCard its sheet takes the tint. ghost keeps a wash at rest rather than being bare, which is the difference between a token and a control: a ghost button has nothing until the pointer arrives.
glass is the default rather than solid. A filter bar is a row of chips, and a row of gradient keys is a row in which nothing is the primary action because everything is.
selected
Chosen moves the chip one step up the ladder its own variant already sits on, rather than changing the colour family: a filter that is on is still the same filter.
solid has no opacity ladder to climb, because a gradient fill is the fill. So it answers the other way the design language allows, it casts its own colour onto the sheet under it. A chosen key lifts; an unchosen one lies flat.
startIcon, endIcon and count
count is drawn on its own small plate, so "Errors 12" reads as one token with a count rather than as two words.
color
size
A chip sits one step down the control ladder from everything else: a md chip is a sm control, 32px rather than 40px. At full control height a glass chip and a glass button are the same object, and a screen full of them says nothing about which one can be pressed.
Accessibility
- A chip with
onClickis a real<button>carryingaria-pressed, so a filter that is on says so. A chip without one adds no role and takes no tab stop. An inert<span>with a click handler on it is the single most common way a component library loses its keyboard users. - The label and the delete button are two separate tab stops, and neither is nested inside the other.
- The delete button has an accessible name already;
deleteLabelis what changes it. disabledstops the label from being a button at all rather than leaving a focusable one that does nothing, and marks the shellaria-disabledso the state is still announced.
- A chip with
onPressedis announced as a button and reports whether it is selected, so a filter that is on says so. A chip without one adds no role and takes no focus stop. - The label and the delete affordance are two separate focus stops, and neither is inside the other.
- The delete affordance has a name already, "Remove";
deleteLabelis what changes it. - Enter, Space and the numpad Enter activate a pressable chip. They are bound on the chip itself, so it behaves the same with or without an app widget above it.
disabledtakes the chip out of the focus order and stops it firing, and the delete affordance with it.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
onClick | onPressed | Flutter's name. |
onDelete | onDeleted | Flutter's name for the same slot. |
a <button> inside a <span> | two sibling focus stops | The same shape for the same reason. HTML forbids the nesting; here a nested recogniser would take one tap twice. |
count as a ReactNode | count as a Widget? | The same thing, spelled in Flutter. |
className, style | — | There is no class list and no style attribute to pass through. |