File item
Shows one file — a glyph that says its format or a small picture of it, its name, and what the product says of it — with the actions to remove it and to try it again, as a row of a column of files or as a tile of a strip of pictures. It can be pressed to open the file. It never reads, keeps or sends the file itself.
- Category
- Communication
- Tokens
comp.file-item- Level
- Base
- Used in
- Chat composer, File upload
- Web
tanrei-file-itemschedule - SwiftUI
TanreiFileItemradio_button_unchecked - Jetpack Compose
TanreiFileItemradio_button_unchecked - Flutter
TanreiFileItemradio_button_unchecked - React Native
FileItemradio_button_unchecked
Generated from docs/status/status.json, specs/file-item.spec.json
One file as a row, and files as tiles
A file item is one file, shown. Before its name stands a glyph that says the file’s format, or a small picture of the file. Below its name is one line with what your product says of it: its format, its size, where its sending stands, or what went wrong. At its end are the two actions a file has: remove it, and try it again.
It has two forms:
row, the default: one line across the width its place gives. For a column of files.tile: a box of a width and a height you give, with the name beneath it. For a strip of pictures.
It shows, and does nothing else
A file item reads no byte of a file, keeps no file and sends nothing. It removes nothing either: its remove action tells you that the person asked, and you take the item out. What it shows is what its props say.
Used in the file upload, where every chosen file is a file item. Use it by itself wherever your product shows files that it holds: the attachments of a message, the documents of a record, the files of a request that was already sent.
Use something else when:
- People choose files and you list them. That is a file upload, which holds file items for you.
- People put the files in an order. That is a reorderable list whose items hold the file items; see Files in an order people change.
- People select among files. That is a list with a selection. A file item has no selected state.
- The picture is the content, to be looked at. A file item’s picture stands where a glyph would and has no alternative text.
On the web it is <tanrei-file-item>. The other platforms have not implemented it yet.
Give name, and the texts you have. value is what click, remove and retry give back to you.
<ul class="attachments" aria-label="Attachments">
<li>
<tanrei-file-item value="a" name="brief.pdf" format-text="PDF" size-text="820 KB"
href="/files/brief.pdf" remove-label="Remove {name}"></tanrei-file-item>
</li>
<li>
<tanrei-file-item value="b" name="floor-plan.png" size-text="3.4 MB"
status="uploading" status-text="Uploading" progress="0.4"
remove-label="Remove {name}" retry-label="Retry {name}"></tanrei-file-item>
</li>
</ul>
<script type="module">
import '@tanrei/web/components/file-item';
const list = document.querySelector('.attachments');
// A file item never removes itself. You take it out, stop sending the file, and move focus.
list.addEventListener('remove', (event) => {
cancel(event.detail.value);
event.target.closest('li').remove();
});
list.addEventListener('retry', (event) => send(event.detail.value));
</script>import { FileItem } from '@tanrei/react';
<ul aria-label="Attachments">
{files.map((file) => (
<li key={file.id}>
<FileItem value={file.id} name={file.name} sizeText={file.sizeText}
status={file.status} statusText={file.statusText} progress={file.progress}
errorText={file.errorText} retryable={file.retryable}
removeLabel="Remove {name}" retryLabel="Retry {name}"
onRemove={() => remove(file.id)} onRetry={() => retry(file.id)} />
</li>
))}
</ul>removeLabel and retryLabel can be given once for the whole product with setWords(). Every other text is the instance’s.
A row in each status: waiting, sent to 40%, under way, done, failed and retryable, failed for good
Anatomy
Anatomy
row, as the web package draws it.| Part | Description |
|---|---|
1 container | The body: the glyph or the picture, and the texts. One part, and the one that is pressed where the file item is pressable. The actions stand beside it, never inside it. |
2 media | The box of the glyph or the picture: the leading box of a row, the whole box of a tile. |
3 icon · optional | The glyph of the file's format, by its name in the system's icon set; or the product's own glyph. Hidden from assistive technology. |
4 preview · optional | The picture of an image file, in the glyph's place. |
5 headline · optional | The file's name. |
6 supporting-text · optional | The second line: the format, the size and the status in words, or the error text in the status's place. |
7 status-icon · optional | The mark before the words of a file that is done or failed. |
8 progress · optional | A thin bar while the file is being sent: how far it has come, or that it is under way. |
9 action · optional | The actions: retry (while it failed and can be tried again) and remove. Icon-only, each with its own state layer and focus ring. |
10 focus-ring | The system's focus indicator around the body or an action that has keyboard focus. |
Properties
| Property | Type | Default | Description |
|---|---|---|---|
form | row | tile | "row" | How the file is shown. row: one line across the width the layout gives — the glyph or a small picture, the name, the second line, the bar, the actions at the end — for a column of files. tile: a box of a width and a height, holding the picture or the glyph, with the texts beneath it and the actions at its corner — for a strip of attached pictures. The product chooses by where the file stands. A design system may add values. |
value | string | "" | What identifies the file to the product: given back by click, remove and retry. |
name | string | "" | The file's name as the person knows it ("report-2026.pdf"). The file item's name for assistive technology in every form, also where it is not drawn. |
mediaType | string | "" | The file's kind as the platform reports it ("image/png"), from which the glyph is chosen. Empty, the kind is read from the end of name. |
glyph | string | "" | The name of a glyph of the system's icon set, drawn in place of the one the file's format would choose. Empty lets the format choose. |
formatText | string | "" | The file's format in the product's words ("PDF", "Spreadsheet"), drawn first on the second line. Empty draws none: the file item has no word for a format. |
sizeText | string | "" | The file's size as text, formatted by the product for the reader's locale ("1.2 MB"). Empty draws none. |
status | pending | uploading | done | error | "pending" | Where the file stands. pending: chosen or kept, and nothing is happening to it — the whole life of a file in a form that is submitted as one, and of a file that is only shown. uploading: the product is sending it; the bar is drawn. done: the product has it. error: it failed or was refused; errorText says why. |
statusText | string | "" | The status in the product's words ("Waiting", "Uploading", "Uploaded"), drawn last on the second line and read with the file. The mark and its color are never the only sign of a status. |
progress | number | null | How far the sending has come, from 0 to 1, while status is uploading. Null draws the bar as under way without saying how far. Ignored in every other status. |
errorText | string | "" | Why the file failed and what to do, in the product's words ("Larger than 10 MB", "The connection was lost"). Drawn in the status's place while status is error, in every form, and never cut. |
retryable | boolean | false | Whether a failed file offers the retry action. True for a failure that trying again may cure (a lost connection); false for one it cannot (a file that is too large). |
preview | boolean | false | Whether a picture of the file is drawn in the glyph's place, where the file is an image and the product gave an address (previewSrc) or the file itself in the platform's idiom. False draws the glyph, also for an image. The picture is drawn by the platform; the file item reads no file. |
previewSrc | string | "" | The address of a picture of the file, as the platform's images take one: a small rendition the product's server made of a file it already has. With preview it is drawn for a file of any format. Empty, the picture comes from the file itself where the product gave it. |
fit | cover | contain | "cover" | How the picture fills its box. cover: it fills the box and what does not fit is cut at the edges, for photographs in a strip. contain: the whole picture is drawn inside the box, on the box's ground, for a picture whose edges matter — a scan, a signature, a diagram. |
nameHidden | boolean | false | In the tile form: the name is not drawn beneath a tile that draws a picture, for a dense strip of pictures. The name stays the file item's name for assistive technology, and is drawn all the same wherever no picture is (FIT-8). Not read in the row form. |
width | number | null | In the tile form: the width of the box, in the platform's layout unit. Null makes it as wide as it is high. |
height | number | null | In the tile form: the height of the box, in the platform's layout unit. Null reads comp.file-item.tile.media.height. |
interactive | boolean | false | Makes the body — the glyph or the picture, and the texts — one pressable action that emits click, for a product that opens the file where it is: an image in an Image preview, a document in a viewer. The actions stay their own. A file item whose action is to go to the file takes href instead and is a link. |
href | string | "" | The destination: the address of the file, as the platform's links take one. Not empty, it makes the body a link — to a file the product already has, to open or to download — whether interactive is set or not. |
target | same | new | "same" | Where the destination opens. same: where the person is. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which a row marks and targetLabel says. Ignored without href. |
targetLabel | string | "" | What assistive technology reads after the link's name while target is new ("opens in a new tab"). The product's words. |
removeLabel | string | "" | The accessible name of the remove action: the whole name in the product's language with one place, {name}, which the file item fills with the file's name ("Remove {name}", "{name}を削除"), so that the actions of two files are told apart by their names and the product still writes one word, not one per file. Empty reads the product's words. |
retryLabel | string | "" | The accessible name of the retry action, with the place {name} filled by the file's name as removeLabel is ("Retry {name}", "{name}を再試行"). Empty reads the product's words. |
disabled | boolean | false | The actions are disabled and the body cannot be pressed. What the file item shows of the file stays as it is and stays readable. |
readOnly | boolean | false | The file is shown and cannot be taken back: no remove action and no retry action is drawn. For a file of a record that is only read. The body can still be pressed where interactive or href says so. |
Slots
| Slot | Description |
|---|---|
icon | A glyph of the product's own in place of the format's glyph, where a name in the system's icon set (glyph) is not enough. Decorative. |
Events
| Event | Description |
|---|---|
click | The body of a pressable file item was activated by pointer, touch, keyboard or assistive technology. Carries value. A link emits it before its destination opens; a product that takes the navigation itself cancels it. |
remove | The person asked to take this file back, by its remove action. Carries value. The file item removes nothing: whoever holds it removes it — and stops sending the file, if it was — and moves focus. |
retry | The person asked to try a failed file again, by its retry action. Carries value. The product sends the file again and sets status. |
States
Behavior
Requirements in EARS form. Every platform implementation is tested against these ids.
| ID | Requirement |
|---|---|
FIT-1 | THE file item SHALL show one file in the form that form names: a row (FIT-2) or a tile (FIT-3). In both it SHALL draw the same parts from the same props — the glyph of the file's format or a picture of the file (FIT-5, FIT-6), its name, a second line (FIT-4), a bar while it is sent (FIT-10) and its actions (FIT-11) — so that a form changes where the parts stand and how large they are, never what is known of the file. THE file item SHALL draw no container, fill or edge around the whole, SHALL hold one file and never a list of files, and SHALL NOT sort, group, number or count itself among others: how file items are arranged is the layout's, or the component's that holds them. |
FIT-2 | WHILE form is row THE file item SHALL be as wide as its layout gives it and as tall as its parts, and SHALL lay out from its start edge: the glyph in a box of comp.file-item.row.icon.size — or, WHILE preview is true, a square of comp.file-item.row.media.height with corners of comp.file-item.row.media.shape and the ground comp.file-item.media.color, holding the picture, or the glyph centred where no picture is drawn (FIT-6); comp.file-item.row.container.gap after it the texts, taking the room that is left; and comp.file-item.row.container.gap after them the actions (FIT-11), with the glyph or the square and the actions centred on the row's first two lines. The first line SHALL be name in comp.file-item.headline.typescale and comp.file-item.headline.color, drawn whole: it SHALL wrap, breaking inside a word where it must, and SHALL never be cut with an ellipsis, because the end of a file's name says what kind of file it is and a row has the room. WHILE form is row THE file item SHALL NOT read width, height or nameHidden, and in debug builds SHALL report nameHidden. |
FIT-3 | WHILE form is tile THE file item SHALL draw a box height high — comp.file-item.tile.media.height WHILE height is null — and width wide, as wide as it is high WHILE width is null, with corners of comp.file-item.tile.container.shape and the ground comp.file-item.media.color, holding the picture clipped to those corners (FIT-6) or, where no picture is drawn, the glyph centred at comp.file-item.tile.icon.size; and beneath the box, comp.file-item.tile.container.gap from it and as wide as it, the name on one line (FIT-9) and the second line (FIT-4). The file item SHALL be exactly as wide as the box. It SHALL draw the actions inside the box at its top end corner, comp.file-item.tile.action.inset from the top and from the end edge, the retry action before the remove action (FIT-11), each on a disc of the tile's action container color so that it is read over any picture; and SHALL draw the box no narrower than both actions, the space between them and the inset on each side need, and no lower than one action and the inset above and below it, in debug builds reporting a width or a height that is less. WHERE its layout gives the tile less room than width THE file item SHALL draw the box as wide as that room and as high as keeps the proportion of width to height, and SHALL never be wider than its layout. |
FIT-4 | THE file item SHALL draw below the name one line in comp.file-item.supporting-text.typescale that wraps and is never cut: formatText, sizeText and statusText, each WHILE it is not empty, in that order, with a middle dot and a space on each side between two that are drawn, in comp.file-item.supporting-text.color; and WHILE status is error and errorText is not empty, errorText in statusText's place, in comp.file-item.error.supporting-text.color. WHILE status is done or error THE file item SHALL draw before the words of the status or the error, comp.file-item.status.icon.gap from them, a mark at comp.file-item.status.icon.size by its name in the system's icon set and through the platform's icon helper — the checked-circle glyph in comp.file-item.done.icon.color WHILE status is done, the error glyph in comp.file-item.error.icon.color WHILE status is error — and SHALL keep the mark from assistive technology, which is told the status by the words. WHILE the line would hold nothing THE file item SHALL draw none and keep no room for it. A status SHALL never be told by the mark or its color alone: WHILE status is done or error and the product gives no words for it (statusText, errorText) THE file item SHALL still render, with the mark alone on the line, and in debug builds report it. THE file item SHALL draw the second line in every form and whatever nameHidden says: what went wrong is never hidden. |
FIT-5 | THE file item SHALL choose the glyph from the file's format, in one of ten kinds, by mediaType — read without regard to case, without anything after a semicolon and without the white space around it — and, WHERE mediaType is empty, is application/octet-stream or is on no list below, by the end of name after its last dot, without regard to case, the first kind that matches in this order, each kind by a closed list of media types and a closed list of endings: pdf (application/pdf; pdf); spreadsheet (text/csv, text/tab-separated-values, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.oasis.opendocument.spreadsheet, application/vnd.apple.numbers, application/x-iwork-numbers-sffnumbers; xls, xlsx, ods, numbers, csv, tsv); presentation (application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.oasis.opendocument.presentation, application/vnd.apple.keynote, application/x-iwork-keynote-sffkey; ppt, pptx, odp, key); archive (application/zip, application/x-zip-compressed, application/x-tar, application/gzip, application/x-gzip, application/x-bzip2, application/x-xz, application/x-7z-compressed, application/vnd.rar, application/x-rar-compressed; zip, tar, gz, tgz, bz2, xz, 7z, rar); code (text/html, text/css, text/javascript, application/javascript, application/x-javascript, text/typescript, application/typescript, text/jsx, text/tsx, application/json, application/xml, text/xml, text/yaml, text/x-yaml, application/yaml, application/x-yaml, application/x-sh, text/x-sh, text/x-shellscript, text/x-python, text/x-script.python, text/x-ruby, application/x-ruby, text/x-go, text/x-rust, text/x-java, text/x-java-source, text/x-kotlin, text/x-swift, text/x-c, text/x-chdr, text/x-csrc, text/x-c++, text/x-c++src, text/x-csharp, text/x-php, application/x-php, application/x-httpd-php, application/sql, text/x-sql; html, css, js, mjs, ts, tsx, jsx, json, xml, yaml, yml, sh, py, rb, go, rs, java, kt, swift, c, h, cpp, cs, php, sql); image (every media type that begins with image/; png, jpg, jpeg, gif, webp, avif, svg, bmp, tif, tiff, heic, heif, ico); video (every media type that begins with video/; mp4, mov, m4v, webm, mkv, avi); audio (every media type that begins with audio/; mp3, wav, m4a, aac, ogg, oga, flac, opus); document (every media type that begins with text/ and is on no list above, application/rtf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.oasis.opendocument.text, application/vnd.apple.pages, application/x-iwork-pages-sffpages; txt, md, rtf, doc, docx, odt, pages); and other for everything else, a name without a dot among them. A media type that is on no list SHALL choose nothing by itself: the ending decides, and then other. video/mp2t SHALL choose nothing by itself either, though it begins with video/: platforms guess it from the ending .ts, which TypeScript files share with MPEG transport streams, so the media type is no surer than the ending and the ending decides. The lists are the whole of the rule on every platform, so that one file draws one glyph wherever it is shown; a platform SHALL add no media type and no ending of its own. It SHALL draw, by its name in the system's icon set and through the platform's icon helper: picture_as_pdf, table, slideshow, folder_zip, code, image, movie, music_note, description and draft, in the order of the kinds. WHILE glyph is not empty THE file item SHALL draw that glyph in the format's place, and WHILE the icon slot holds a glyph SHALL draw the slot's in the place of both. THE file item SHALL draw every such glyph in comp.file-item.icon.color, whatever the format — a format has no color of its own, because a color says one thing wherever it is used and a format is not a status — and SHALL keep it from assistive technology: the name says which file it is, and formatText says its format in words where the product has them. THE file item SHALL NOT open the file to learn its format, and SHALL NOT change the glyph because of status. |
FIT-6 | WHILE preview is true THE file item SHALL draw a picture of the file in the glyph's place: the picture at previewSrc WHILE previewSrc is not empty, whatever the file's format; otherwise, WHILE the file's kind is image (FIT-5) and the product gave the file item the file itself in the platform's idiom, the platform's own drawing of that file (FIT-7). It SHALL fill the box as fit names — cover: scaled to fill the box and centred, what does not fit cut at the box's edges; contain: scaled to stand whole inside the box, centred on the box's ground — never stretched out of its proportion and never drawn larger than the box. Until the picture can be drawn, and IF it cannot be drawn — an address that does not answer, a format the platform does not draw, a file that is not a picture after all — THE file item SHALL draw the glyph of the file's format in its place, SHALL draw no sign of loading and no sign of the failure, SHALL NOT set status and SHALL emit nothing for it, and in debug builds SHALL report a previewSrc that could not be drawn. WHEN the picture can be drawn THE file item SHALL draw it at once in the glyph's place, without a motion and without changing the size of anything. THE file item SHALL keep the picture from assistive technology: it stands where the glyph stood, and the file is named by name. WHILE preview is false, WHILE preview is true and neither an address nor a file was given, and for a file that is not an image and has no previewSrc, THE file item SHALL draw the glyph. THE file item SHALL draw no picture of a video, of a page of a document or of anything else it would have to open and read to draw. |
FIT-7 | THE file item SHALL NOT read a file: it SHALL open no connection for a file, SHALL NOT read a file's bytes into memory of its own, SHALL NOT copy, store, resize or encode a file, and SHALL make of the file the product gave it nothing but what the platform needs to draw the picture of FIT-6. WHERE the platform draws a picture of a file from an address it makes for the file's handle (the web's object URL) THE file item SHALL make that address itself, at most one per file item; SHALL make it only WHILE FIT-6 has it draw the picture from the file — preview is true, previewSrc is empty, the kind is image — and only once the file item is in or near what is in view; SHALL give it to nothing but its own picture; and SHALL release it WHEN the file item is taken out of the page or the screen, WHEN the product gives it another file or none, and WHEN preview becomes false, previewSrc is given or the kind is no longer image. WHERE the platform makes a small picture of a file itself (a thumbnail the system gives for a handle) THE file item SHALL ask for one no larger than its box and SHALL cancel the request on the same occasions. A file item whose preview is false SHALL cost what its texts cost: no address, no picture and no request is made for it, however many there are. |
FIT-8 | WHILE form is tile and nameHidden is true and the box draws a picture THE file item SHALL NOT draw the name beneath the box and SHALL keep no room for it. WHILE the box draws no picture — preview is false, the picture is not drawn yet or cannot be drawn — THE file item SHALL draw the name whatever nameHidden says, because a glyph of a format does not tell two files of that format apart. In every case name SHALL stay the file item's name for assistive technology (FIT-19), and the second line SHALL be drawn (FIT-4). IF name is empty THE file item SHALL still render and in debug builds report that the file has no name. |
FIT-9 | WHILE form is tile THE file item SHALL draw the name on one line in comp.file-item.headline.typescale and comp.file-item.headline.color, no wider than the box. WHERE the name does not fit THE file item SHALL cut it in the middle and never at its end: the ending of the name — from its last dot, WHERE that dot is not the name's first character and is among its last eight — SHALL stay drawn, the start of the name SHALL be drawn as far as the room that is left allows, and an ellipsis SHALL stand between the two, so that two pictures from one camera are still told from a document of the same name; a name with no such ending SHALL be cut at its end, with an ellipsis. The ellipsis is a mark of the platform's text layout and not a word. The whole name SHALL reach assistive technology, and the cut SHALL be made in the drawing alone: THE file item SHALL NOT change name. The second line of a tile SHALL wrap inside the box's width and SHALL never be cut (FIT-4). |
FIT-10 | WHILE status is uploading THE file item SHALL draw a bar comp.file-item.progress.thickness high: a track in comp.file-item.progress.track.color and over it, from the track's start, an indicator in comp.file-item.progress.indicator.color across the fraction progress gives, clamped to 0 and 1, moving to a new value over comp.file-item.motion.progress-duration with comp.file-item.motion.progress-easing. WHILE form is row the bar SHALL stand comp.file-item.progress.gap below the texts and as wide as them, with ends rounded by comp.file-item.progress.shape; WHILE form is tile it SHALL lie along the bottom edge of the box, inside it and as wide as it, clipped by the box's corners, over the picture or the ground. WHILE progress is null THE file item SHALL draw the bar as a linear progress indicator draws its indeterminate form (PRG-2), once per comp.file-item.motion.cycle-duration with comp.file-item.motion.cycle-easing, and SHALL NOT run it while the file item is not visible. WHERE reduced motion is requested THE file item SHALL show a new value at once and draw the indeterminate bar as PRG-7 says. In every other status THE file item SHALL draw no bar and, in a row, keep no room for one. THE file item SHALL never move the bar by itself: progress is the product's report of its own sending. |
FIT-11 | WHILE readOnly is false THE file item SHALL draw a remove action, and before it, comp.file-item.action.gap from it, a retry action WHILE status is error and retryable is true: each an icon-only button, a square of comp.file-item.action.size with corners of comp.file-item.action.shape, filled with the action container color of its form, holding the icon set's close glyph (remove) or refresh glyph (retry) at comp.file-item.action.icon.size in the action icon color of its form — at the end of a row (FIT-2), at the top end corner of a tile (FIT-3). WHEN the person activates one by pointer, touch, Enter or Space THE file item SHALL emit remove or retry exactly once with value and do nothing else: it SHALL NOT take itself away, change its status or move focus because of the activation. The actions SHALL NOT be inside the body (FIT-12) and SHALL NOT activate it. Each SHALL be a stop in the focus order after the body, retry before remove; THE file item SHALL handle no arrow key, Delete or Backspace, SHALL hold no selected state and draw no handle, and SHALL NOT be moved by dragging: an order that people change is the List's, in whose rows a file item is content. |
FIT-12 | WHILE interactive is true and href is empty THE file item SHALL make its body — the glyph or the picture, and the texts — exactly one focusable element with the button role, before the actions in the focus order, and WHEN the person activates it by pointer, touch, Enter or Space SHALL emit click exactly once with value. It SHALL open nothing itself: the product opens the file where it means to. WHILE interactive is false and href is empty THE file item's body SHALL NOT be focusable, SHALL NOT emit click, and SHALL draw no hover, focus or press answer. In a tile the body is the box and the texts beneath it; a press on an action's visible bounds SHALL go to that action and never to the body under it (FIT-18). |
FIT-13 | WHILE href is not empty THE file item SHALL make its body a link, whether interactive is true or false, as every control with a destination is: the platform's own link to href, with the link role and not the button role; activated by pointer, touch or Enter and not by Space; emitting click exactly once with value and then opening href where target names, unless the product cancelled that click, and never holding the navigation back for sys.motion.duration.press-hold; drawn from the same tokens in every state as a pressable body. WHILE target is new THE file item SHALL open href in a new context, SHALL expose targetLabel as the link's description after its name and role — in debug builds reporting an empty one — and WHILE form is row SHALL draw, comp.file-item.row.container.gap after the texts and before the actions, the glyph open_in_new of the system's icon set through the icon helper at comp.file-item.action.icon.size in comp.file-item.icon.color, hidden from assistive technology; a tile has no such place and draws none. WHILE disabled is true THE file item's link SHALL NOT be followed by any means the platform gives a link, SHALL keep the link role with the disabled state and no destination, and SHALL NOT be focusable. IF href is only white space, names a script to run, or cannot be read as an address THE file item SHALL be what interactive makes it, without a destination, and in the last two cases report it in debug builds. THE file item SHALL load nothing from href and SHALL NOT learn the file's name, size or format from it. |
FIT-14 | WHILE the body of a pressable file item is hovered, keyboard-focused or pressed THE file item SHALL render the body's container color from the component tokens of its form in the highest-precedence active state, with precedence pressed over focused over hovered, as a layer over the glyph or the picture and the texts — in a row over the body's bounds grown by comp.file-item.row.container.outset on every side, with corners of comp.file-item.row.container.shape, taking no room; in a tile over the box, with the box's corners. WHILE an action is hovered, keyboard-focused or pressed THE file item SHALL render that action's container, glyph and opacity from the action parts of the same tokens, with the same precedence. Focus gained by pointer or touch SHALL NOT select the focused state. The name, the second line, the glyph, the picture, the bar and the part that is not under the pointer, the press or the focus SHALL NOT change. WHEN the body or an action receives keyboard focus THE file item SHALL show the focus ring around it without animation — the system's focus indicator, sys.focus.offset from the part and following its corners, sys.focus.outer-width wide in the color role focus-ring, with the inner stroke of sys.focus.inner-width where the system has one; focus gained by pointer or touch SHALL NOT show it. |
FIT-15 | WHEN the body of a pressable file item or one of its actions is pressed by pointer, touch, Enter or Space THE file item SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner. A body that is a link is not pressed by Space (FIT-13). |
FIT-16 | WHILE the body of a pressable file item or one of its actions is pressed THE file item SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole part and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; glyph, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.file-item.motion.feedback-duration with comp.file-item.motion.state-easing, fades over comp.file-item.motion.state-duration with comp.file-item.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced. |
FIT-17 | THE file item SHALL ease the corners of a tile's box, of a row's picture, of the body's state layer and of its actions by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show the keyboard focus of its body and of an action as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. |
FIT-18 | WHERE the visual size of an action, or of the body of a pressable file item, is below the minimum target THE file item SHALL extend its hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size; a platform that cannot tell the input type SHALL use sys.target.touch. WHILE the extended hit areas of two parts overlap — the retry and the remove action, an action and the body, or the actions of two file items — THE file item SHALL give a press on a part's visible bounds to that part, a press on an action's visible bounds to the action even where it lies over the body, and a press between two actions to the nearer one. THE file item SHALL set two actions that stand side by side — the retry and the remove action — apart by comp.file-item.action.gap, or by twice the amount by which the minimum target in force exceeds comp.file-item.action.size where that is more, so that the hit area of each reaches the minimum target on its own half of the space between them; it SHALL NOT shrink the hit area of either below the minimum target to keep them nearer, and the hit area of the action at the item's end SHALL still reach no further than the item's end edge. |
FIT-19 | THE file item SHALL expose itself to assistive technology as a group named by name, in every form and whatever nameHidden says, read in this order: name; formatText, sizeText, then statusText or — WHILE status is error — errorText; then, WHILE status is uploading, the bar as a progress bar named by name whose value is progress as a share of 0 to 1, or busy WHILE progress is null; then the retry action WHERE it is drawn, as a button named retryLabel, and the remove action WHERE it is drawn, as a button named removeLabel, each with its place {name} filled by name, so that "Remove brief.pdf" is the action's name and two files' actions are told apart wherever names are listed; neither action SHALL carry the name a second time as a description. WHILE the body is pressable it SHALL be a button, or a link (FIT-13), named by name and described by the second line's texts. WHILE a component holds the file item as one of the items of a list of its own THE file item SHALL be exposed as an item of that list in the group's place, by the word of that component and through no prop of its own. The glyph, the picture, the status mark and the mark of a new context SHALL be kept from assistive technology. THE file item SHALL have no live region and SHALL announce nothing — not that it appeared, not a change of status or of progress: what is said of many files is said once, by whoever holds them. |
FIT-20 | THE file item SHALL have no words of its own: it SHALL take removeLabel and retryLabel from the instance's prop and, WHILE that is empty, from the product's words of the keys removeLabel and retryLabel, and name, formatText, sizeText, statusText, errorText and targetLabel from the product alone, and from nowhere else. IF neither gives removeLabel or retryLabel THE file item SHALL still draw the action, expose it without a name, and in debug builds report the missing word once per page or screen, naming the prop. IF the word given has no place {name} THE file item SHALL name the action with it as it is written and in debug builds report that every file's action has the same name; another text between braces, and a brace that opens or closes nothing, SHALL be kept as written and reported. THE file item SHALL build no word from mediaType, from the end of name or from a number: it SHALL NOT write a format, a size or a share of progress in words. |
FIT-21 | WHILE disabled is true THE file item SHALL render each action from the disabled tokens of its form and disable it, so that it leaves the focus order — an action keeps the container it has at rest, none in a row and the disc in a tile, and only its glyph fades, as a quiet icon-only button grows no fill when it is disabled; SHALL make the body not pressable and not focusable, drawing no hover, focus or press answer on it; and SHALL emit neither click, remove nor retry. The name, the second line, the glyph, the picture, the status mark and the bar SHALL stay drawn as they are and readable by assistive technology: what was chosen is still what was chosen. |
FIT-22 | WHILE readOnly is true THE file item SHALL draw neither the remove action nor the retry action, SHALL keep no room for them — a row's texts reach its end edge — and SHALL emit neither remove nor retry. Everything else SHALL hold as without it: the status, the bar and the error are drawn, and the body is pressable WHERE interactive or href makes it so. |
FIT-23 | WHEN the colors of the body or of an action change between states THE file item SHALL animate the change over comp.file-item.motion.state-duration with comp.file-item.motion.state-easing, and SHALL change them at once where motion is reduced. A change of form, of status, of a text, of the glyph or of the picture SHALL be drawn at once, without a motion of the file item's own. THE file item SHALL NOT run a picture that moves: WHERE the platform can hold a moving picture still it SHALL draw its first frame, and where it cannot (the web's image element) it SHALL leave the picture to the platform and state that in its README. |
FIT-24 | WHILE the text size is enlarged THE file item SHALL grow with its texts: a row's lines wrap and are never cut, its glyph keeps its box, its picture comp.file-item.row.media.height and its actions comp.file-item.action.size; a tile's box keeps the size the product or the token gives, its name stays one line and grows in height, and its second line wraps beneath it. WHERE what stands beside a row's texts — its glyph or picture, the mark of a new context, its actions and the gaps between them — would leave the texts less than half the width of the row (a narrow screen, enlarged text) THE file item SHALL place the actions on a line of their own below the texts and the bar, at the row's end and in the same order, the line as tall as the minimum target in force, so that the name and the second line take the whole width after the glyph; otherwise a row's actions SHALL stay at its end beside its first lines. The answer SHALL be the same wherever the actions stand now, so that placing them does not change it. At 200 % in a width of 320 no text SHALL be lost or lie over another part, a tile SHALL be no wider than its layout (FIT-3), and nothing SHALL need scrolling in two directions. |
FIT-25 | WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE file item SHALL draw its texts and glyphs in the platform's text color, a link's name in the platform's link color; SHALL draw the bar with its indicator in the platform's highlight color on a track edged in the text color; SHALL draw a tile's box and a row's picture box with an edge in the text color, since their ground is gone; SHALL draw each action of a tile on the platform's surface color with an edge in the text color; SHALL draw disabled actions in the platform's disabled text color; SHALL leave a picture as it is; and SHALL rely on no color of its own to tell a failed file from another: its words and its mark's shape do. |
FIT-26 | WHERE the layout direction is right-to-left THE file item SHALL mirror its layout: a row's glyph or picture at the right, its texts after it toward the left, the mark of a new context and the actions at the left with remove outermost; a tile's actions at the top left corner of its box with remove outermost; the status mark at the right of its words; and the bar filling — and its indeterminate segment moving — from right to left. A picture SHALL never be mirrored, and a ripple SHALL still grow from the point of contact. |
FIT-27 | THE file item SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the file item is shown THE file item SHALL draw itself for the new direction at once, without a motion of its own, keeping its focus. |
FIT-28 | THE file item SHALL lay out each text it draws — the name, the format, the size, the status and the error — as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a file name in Latin letters keeps its order, its dot and its extension in a right-to-left row, and the cut of FIT-9 keeps the name's own ending wherever that is drawn. WHERE the layout direction is right-to-left a glyph SHALL be mirrored exactly when the system's icon set marks its name as mirrored or the product forces it, by the icon helper and inside its own box; THE file item SHALL mirror no glyph itself. |
FIT-29 | WHEN form, status, fit or target names a value the file item does not know THE file item SHALL behave as row, pending, cover and same; IF width or height is given and is not a number above zero THE file item SHALL behave as if it were null; IF glyph names a glyph the icon set does not have THE file item SHALL draw nothing in its box and keep the box; and in each case debug builds SHALL report it. IF preview is true WHILE previewSrc is empty and the product gave no file THE file item SHALL draw the glyph and report nothing: a product may give the file later. |
FIT-30 | WHEN a prop changes while the file item is shown THE file item SHALL draw and expose the new value at once, keeping its focus, and SHALL announce nothing (FIT-19). WHEN the retry action leaves — status is no longer error, retryable or readOnly changed — while focus is on it THE file item SHALL move focus to its remove action, or to its body WHERE that is pressable and no remove action is drawn. THE file item SHALL NOT move focus otherwise, and where focus goes when a file item itself is taken away is its holder's to say. |
FIT-31 | WHILE the system's icon set cannot yet draw a glyph (its font is still loading) THE file item SHALL draw nothing in that glyph's place, never the glyph's name as text, SHALL keep the glyph's box, and SHALL draw the glyph once the set can; removeLabel and retryLabel SHALL still reach assistive technology. |
FIT-32 | IF the file item holds a child that is not the glyph of its icon slot THE file item SHALL NOT draw it and in debug builds report it: the file item has no place for content of the product's — a menu of further actions, a caption, a checkbox — and a product that needs one puts it beside the file item, in its own layout or in the trailing place of a List's row. A platform that cannot inspect its content SHALL state the rule in its README and docs page. |
FIT-33 | THE file item SHALL NOT upload and SHALL NOT know more of a file than its props say: it SHALL send no file or part of one to anyone, SHALL keep no file beyond the handle the product gave it for FIT-6, SHALL set neither status nor progress itself, SHALL take name and mediaType, WHERE the product left them empty and gave the file itself, from what the platform reports of that file without opening it, and nothing else from it, and SHALL load nothing about a destination. This holds on every platform. |
Platform name mapping
Parity does not mean identical APIs. Each platform uses its own idiom for the same contract.
| Spec | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| Component | tanrei-file-item | TanreiFileItem | TanreiFileItem | TanreiFileItem | FileItem |
form · Property | form | form | form | form | form |
value · Property | value | id | key | key | value |
name · Property | name | name | name | name | name |
mediaType · Property | mediaType | mediaType | mediaType | mediaType | mediaType |
glyph · Property | glyph | glyph | glyph | glyph | glyph |
formatText · Property | formatText | formatText | formatText | formatText | formatText |
sizeText · Property | sizeText | sizeText | sizeText | sizeText | sizeText |
status · Property | status | status | status | status | status |
statusText · Property | statusText | statusText | statusText | statusText | statusText |
progress · Property | progress | progress | progress | progress | progress |
errorText · Property | errorText | errorText | errorText | errorText | errorText |
retryable · Property | retryable | isRetryable | retryable | retryable | retryable |
preview · Property | preview | showsPreview | showPreview | showPreview | preview |
previewSrc · Property | previewSrc | previewURL | previewModel | previewImage | previewSource |
fit · Property | fit | contentMode | contentScale | fit | resizeMode |
nameHidden · Property | nameHidden | isNameHidden | nameHidden | nameHidden | nameHidden |
width · Property | width | width | width | width | width |
height · Property | height | height | height | height | height |
interactive · Property | interactive | action | onClick | onPressed | onPress |
href · Property | href | destination | href | href | href |
target · Property | target | opens | target | target | target |
targetLabel · Property | targetLabel | targetLabel | targetLabel | targetLabel | targetLabel |
removeLabel · Property | removeLabel | removeLabel | removeContentDescription | removeSemanticLabel | removeAccessibilityLabel |
retryLabel · Property | retryLabel | retryLabel | retryContentDescription | retrySemanticLabel | retryAccessibilityLabel |
disabled · Property | disabled | disabled | enabled | enabled | disabled |
readOnly · Property | readOnly | isReadOnly | readOnly | readOnly | readOnly |
click · Event | click | action | onClick | onPressed | onPress |
remove · Event | remove | onRemove | onRemove | onRemove | onRemove |
retry · Event | retry | onRetry | onRetry | onRetry | onRetry |
| Notes | The shadow-root form: a part with states and actions of its own. Class TanreiFileItem; React FileItem. <tanrei-file-item name="brief.pdf" size-text="820 KB" remove-label="Remove"></tanrei-file-item>; a tile: <tanrei-file-item form="tile" preview width="160" height="90" name="floor-plan.png"></tanrei-file-item>. Attributes media-type, format-text, size-text, status-text, error-text, preview-src, name-hidden, target-label, remove-label, retry-label, readonly; progress absent for null; width and height are numbers of CSS pixels, as an image's, and are the box of the tile alone. **The file**: the property file (a File or null, never an attribute) is the platform's handle. With preview and an image kind and no preview-src, the element creates one object URL for it when it first comes near the viewport (an IntersectionObserver), gives it to its own <img part="preview" alt="" decoding="async"> and to nothing else, and revokes it in disconnectedCallback, when file is set again and when preview, preview-src or the kind stops asking for it (FIT-7); reconnected, it makes a new one when it is next in view. name and media-type left empty are read from file.name and file.type (FIT-33). file is also what a File upload submits for the item (FUP-31). **The body** is <div part="container">, or <button part="container"> with interactive, or <a part="container" href> with href (the shared link primitive; web-only rel and download are forwarded to the anchor, and download is how a link saves the file in place of opening it); the two actions are native buttons beside it in the shadow root, with aria-label — the product's sentence with the file's name filled in — never inside it. In a tile the actions are placed over the box and come after the body in the DOM, so the order of focus is the order read. click, remove and retry are CustomEvents that bubble and are composed, with detail.value; click is cancelable for a link. The host has role="group" and aria-label from name; a File upload that holds it sets role="listitem" on it through an internal channel that is not part of its public surface (FIT-19). The cut of a tile's name (FIT-9) is two spans — the start, which shrinks with an ellipsis, and the ending, which does not — inside one <bdi>. Parts: container, media, icon, preview, headline, supporting-text, status-icon, progress, action. The bar is <tanrei-progress-indicator> (the small linear one), drawn from this component's own bar tokens. No form participation: a file item submits nothing; what holds it does. | TanreiFileItem(name: "brief.pdf", sizeText: "820 KB", onRemove: { }); a tile: TanreiFileItem(name:, form: .tile, width: 160, height: 90, showsPreview: true, file: url). interactive is whether action is given (a nil action is not pressable); destination makes it a Link. The file is a URL: the picture is QLThumbnailGenerator's thumbnail for the box's size at the display's scale, asked for when the view appears and cancelled when it disappears (FIT-7); previewURL goes through AsyncImage. contentMode is .fill for cover and .fit for contain. disabled follows .disabled(_:). The middle cut is .truncationMode(.middle), which keeps the ending. | TanreiFileItem(name = "brief.pdf", sizeText = "820 KB", onRemove = { }). interactive is whether onClick is given; disabled is the inverse flag enabled. The file is a content Uri: the picture is ContentResolver.loadThumbnail(uri, size, signal) for the box's size, started in a LaunchedEffect keyed by the Uri and cancelled when the item leaves composition (FIT-7); previewModel is whatever the product's image loader takes, drawn through a painter slot so that the package depends on no loader. contentScale is Crop for cover and Fit for contain. The middle cut is TextOverflow.MiddleEllipsis. A link has no link role: Compose has none for a clickable element. | TanreiFileItem(name: 'brief.pdf', sizeText: '820 KB', onRemove: () {}). interactive is whether onPressed is given; disabled is the inverse flag enabled. The framework has no thumbnail of a file by its handle: previewImage is an ImageProvider the product gives (a FileImage with ResizeImage for a local picture, a NetworkImage for one its server made), and the file item makes none itself — FIT-7's address is the product's on this platform, and the README says so. fit is BoxFit.cover or BoxFit.contain. The middle cut is drawn by measuring with a TextPainter. A link opens through the opener the app provides. | <FileItem name="brief.pdf" sizeText="820 KB" onRemove={…} />. interactive is whether onPress is given. previewSource is an Image source ({ uri }): for a picked file the product passes the picker's uri, which the platform's image view draws scaled down without the file item reading it; no address is made (FIT-7). resizeMode is cover or contain. The middle cut is ellipsizeMode="middle". A link opens through Linking. |
Tokens
| Token | Aliases | Resolved (light) |
|---|---|---|
--tanrei-comp-file-item-action-gap | sys.space.space200 | 16px |
--tanrei-comp-file-item-action-icon-size | sys.icon.small | 20px |
--tanrei-comp-file-item-action-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-file-item-action-size | — | 32px |
--tanrei-comp-file-item-disabled-opacity | — | 1 |
--tanrei-comp-file-item-done-icon-color | sys.color.success | |
--tanrei-comp-file-item-error-icon-color | sys.color.error | |
--tanrei-comp-file-item-error-supporting-text-color | sys.color.error | |
--tanrei-comp-file-item-focused-opacity | — | 1 |
--tanrei-comp-file-item-headline-color | sys.color.on-surface | |
--tanrei-comp-file-item-hovered-opacity | — | 1 |
--tanrei-comp-file-item-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-file-item-media-color | sys.color.surface-container | |
--tanrei-comp-file-item-motion-cycle-duration | sys.motion.duration.extra-long4 | 1000ms |
--tanrei-comp-file-item-motion-cycle-easing | sys.motion.easing.standard | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-file-item-motion-feedback-duration | sys.motion.role.feedback | 450ms |
--tanrei-comp-file-item-motion-progress-duration | sys.motion.duration.medium2 | 300ms |
--tanrei-comp-file-item-motion-progress-easing | sys.motion.easing.standard | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-file-item-motion-state-duration | sys.motion.role.state.large | 250ms |
--tanrei-comp-file-item-motion-state-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-file-item-pressed-opacity | — | 1 |
--tanrei-comp-file-item-progress-gap | sys.space.space75 | 6px |
--tanrei-comp-file-item-progress-indicator-color | sys.color.primary | |
--tanrei-comp-file-item-progress-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-file-item-progress-thickness | — | 2px |
--tanrei-comp-file-item-progress-track-color | sys.color.surface-variant | |
--tanrei-comp-file-item-row-action-container-color | — | |
--tanrei-comp-file-item-row-action-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-file-item-row-action-state-layer-color | comp.file-item.row.action.icon.color | |
--tanrei-comp-file-item-row-container-color | — | |
--tanrei-comp-file-item-row-container-gap | sys.space.space150 | 12px |
--tanrei-comp-file-item-row-container-outset | sys.space.space50 | 4px |
--tanrei-comp-file-item-row-container-shape | sys.shape.corner.small | 6px |
--tanrei-comp-file-item-row-disabled-action-container-color | — | |
--tanrei-comp-file-item-row-disabled-action-icon-color | — | |
--tanrei-comp-file-item-row-disabled-action-state-layer-color | — | |
--tanrei-comp-file-item-row-disabled-container-color | — | |
--tanrei-comp-file-item-row-disabled-state-layer-color | — | |
--tanrei-comp-file-item-row-focused-action-container-color | — | |
--tanrei-comp-file-item-row-focused-action-icon-color | — | |
--tanrei-comp-file-item-row-focused-action-state-layer-color | — | |
--tanrei-comp-file-item-row-focused-container-color | — | |
--tanrei-comp-file-item-row-focused-state-layer-color | — | |
--tanrei-comp-file-item-row-hovered-action-container-color | — | |
--tanrei-comp-file-item-row-hovered-action-icon-color | — | |
--tanrei-comp-file-item-row-hovered-action-state-layer-color | — | |
--tanrei-comp-file-item-row-hovered-container-color | — | |
--tanrei-comp-file-item-row-hovered-state-layer-color | — | |
--tanrei-comp-file-item-row-icon-size | sys.icon.medium | 24px |
--tanrei-comp-file-item-row-media-height | — | 40px |
--tanrei-comp-file-item-row-media-shape | sys.shape.corner.small | 6px |
--tanrei-comp-file-item-row-pressed-action-container-color | — | |
--tanrei-comp-file-item-row-pressed-action-icon-color | — | |
--tanrei-comp-file-item-row-pressed-action-state-layer-color | — | |
--tanrei-comp-file-item-row-pressed-container-color | — | |
--tanrei-comp-file-item-row-pressed-state-layer-color | — | |
--tanrei-comp-file-item-row-state-layer-color | comp.file-item.headline.color | |
--tanrei-comp-file-item-status-icon-gap | sys.space.space50 | 4px |
--tanrei-comp-file-item-status-icon-size | sys.icon.extra-small | 16px |
--tanrei-comp-file-item-supporting-text-color | sys.color.on-surface-variant | |
--tanrei-comp-file-item-tile-action-container-color | sys.color.surface-container-lowest | |
--tanrei-comp-file-item-tile-action-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-file-item-tile-action-inset | sys.space.space50 | 4px |
--tanrei-comp-file-item-tile-action-state-layer-color | comp.file-item.tile.action.icon.color | |
--tanrei-comp-file-item-tile-container-color | — | |
--tanrei-comp-file-item-tile-container-gap | sys.space.space75 | 6px |
--tanrei-comp-file-item-tile-container-shape | sys.shape.corner.medium | 8px |
--tanrei-comp-file-item-tile-disabled-action-container-color | — | |
--tanrei-comp-file-item-tile-disabled-action-icon-color | — | |
--tanrei-comp-file-item-tile-disabled-action-state-layer-color | — | |
--tanrei-comp-file-item-tile-disabled-container-color | — | |
--tanrei-comp-file-item-tile-disabled-state-layer-color | — | |
--tanrei-comp-file-item-tile-focused-action-container-color | — | |
--tanrei-comp-file-item-tile-focused-action-icon-color | — | |
--tanrei-comp-file-item-tile-focused-action-state-layer-color | — | |
--tanrei-comp-file-item-tile-focused-container-color | — | |
--tanrei-comp-file-item-tile-focused-state-layer-color | — | |
--tanrei-comp-file-item-tile-hovered-action-container-color | — | |
--tanrei-comp-file-item-tile-hovered-action-icon-color | — | |
--tanrei-comp-file-item-tile-hovered-action-state-layer-color | — | |
--tanrei-comp-file-item-tile-hovered-container-color | — | |
--tanrei-comp-file-item-tile-hovered-state-layer-color | — | |
--tanrei-comp-file-item-tile-icon-size | sys.icon.large | 32px |
--tanrei-comp-file-item-tile-media-height | — | 96px |
--tanrei-comp-file-item-tile-pressed-action-container-color | — | |
--tanrei-comp-file-item-tile-pressed-action-icon-color | — | |
--tanrei-comp-file-item-tile-pressed-action-state-layer-color | — | |
--tanrei-comp-file-item-tile-pressed-container-color | — | |
--tanrei-comp-file-item-tile-pressed-state-layer-color | — | |
--tanrei-comp-file-item-tile-state-layer-color | comp.file-item.headline.color | |
--tanrei-comp-file-item-headline-typescale | sys.typescale.body | 400 14px/1.5714 |
--tanrei-comp-file-item-supporting-text-typescale | sys.typescale.body-small | 400 13px/1.5385 |
Fixtures
Canonical renderings. Every platform snapshots each fixture under the same id so the results can be compared.
form: row (31)
row
value: a name: brief.pdf sizeText: 820 KB removeLabel: Remove {name} retryLabel: Retry {name} row-format
value: a name: brief.pdf formatText: PDF sizeText: 820 KB status: pending statusText: Waiting removeLabel: Remove {name} retryLabel: Retry {name} row-uploading
value: b name: floor-plan.png sizeText: 3.4 MB status: uploading statusText: Uploading progress: 0.4 removeLabel: Remove {name} retryLabel: Retry {name} row-uploading-indeterminate
value: c name: survey.pdf sizeText: 1.1 MB status: uploading statusText: Uploading removeLabel: Remove {name} retryLabel: Retry {name} row-done
value: d name: contract.pdf sizeText: 2.0 MB status: done statusText: Uploaded removeLabel: Remove {name} retryLabel: Retry {name} row-error-retryable
value: e name: site-photo.png sizeText: 6.8 MB status: error errorText: The connection was lost. retryable: true removeLabel: Remove {name} retryLabel: Retry {name} row-error
value: f name: scan-archive.pdf sizeText: 48 MB status: error errorText: Larger than 10 MB. removeLabel: Remove {name} retryLabel: Retry {name} kind-image
value: image name: floor-plan.png removeLabel: Remove {name} retryLabel: Retry {name} kind-video
value: video name: walkthrough.mp4 removeLabel: Remove {name} retryLabel: Retry {name} kind-audio
value: audio name: interview.m4a removeLabel: Remove {name} retryLabel: Retry {name} kind-pdf
value: pdf name: brief.pdf removeLabel: Remove {name} retryLabel: Retry {name} kind-spreadsheet
value: spreadsheet name: budget.xlsx removeLabel: Remove {name} retryLabel: Retry {name} kind-presentation
value: presentation name: kickoff.pptx removeLabel: Remove {name} retryLabel: Retry {name} kind-archive
value: archive name: site-photos.zip removeLabel: Remove {name} retryLabel: Retry {name} kind-code
value: code name: config.json removeLabel: Remove {name} retryLabel: Retry {name} kind-document
value: document name: minutes.docx removeLabel: Remove {name} retryLabel: Retry {name} kind-other
value: other name: README removeLabel: Remove {name} retryLabel: Retry {name} row-media-type
value: a name: export mediaType: text/csv sizeText: 12 KB removeLabel: Remove {name} retryLabel: Retry {name} row-glyph
value: a name: keys.pem glyph: key sizeText: 2 KB removeLabel: Remove {name} retryLabel: Retry {name} row-icon-slot
value: a name: model.glb sizeText: 14 MB removeLabel: Remove {name} retryLabel: Retry {name} slots: icon row-preview
value: b name: floor-plan.png sizeText: 3.4 MB preview: true previewSrc: fixtures/avatar.png removeLabel: Remove {name} retryLabel: Retry {name} row-preview-glyph
value: a name: brief.pdf sizeText: 820 KB preview: true removeLabel: Remove {name} retryLabel: Retry {name} row-interactive
value: b name: floor-plan.png sizeText: 3.4 MB preview: true previewSrc: fixtures/avatar.png interactive: true removeLabel: Remove {name} retryLabel: Retry {name} row-link
value: d name: contract.pdf formatText: PDF sizeText: 2.0 MB href: /files/contract.pdf readOnly: true row-link-new
value: d name: contract.pdf sizeText: 2.0 MB href: /files/contract.pdf target: new targetLabel: opens in a new tab removeLabel: Remove {name} retryLabel: Retry {name} row-disabled
value: e name: site-photo.png sizeText: 6.8 MB status: error errorText: The connection was lost. retryable: true removeLabel: Remove {name} retryLabel: Retry {name} disabled: true row-read-only
value: d name: contract.pdf sizeText: 2.0 MB status: done statusText: Uploaded readOnly: true row-without-words
value: e name: site-photo.png status: error retryable: true row-long-text
value: a name: municipal-planning-application-supportin… formatText: PDF sizeText: 9.6 MB status: error errorText: This file is larger than 10 MB. Export i… retryable: true removeLabel: Remove {name} retryLabel: Retry {name} row-rtl
value: b name: floor-plan.png sizeText: 3.4 MB status: uploading statusText: Uploading progress: 0.4 removeLabel: Remove {name} retryLabel: Retry {name} direction: rtl row-rtl-error
value: e name: site-photo.png sizeText: 6.8 MB status: error errorText: The connection was lost. retryable: true href: /files/site-photo.png target: new targetLabel: opens in a new tab removeLabel: Remove {name} retryLabel: Retry {name} direction: rtl form: tile (15)
tile
form: tile value: a name: brief.pdf sizeText: 820 KB removeLabel: Remove {name} retryLabel: Retry {name} tile-preview
form: tile value: b name: floor-plan.png preview: true previewSrc: fixtures/avatar.png removeLabel: Remove {name} retryLabel: Retry {name} tile-preview-pending
form: tile value: b name: floor-plan.png preview: true removeLabel: Remove {name} retryLabel: Retry {name} tile-size-cover
form: tile value: b name: floor-plan.png preview: true previewSrc: fixtures/avatar.png width: 160 height: 90 removeLabel: Remove {name} retryLabel: Retry {name} tile-size-contain
form: tile value: b name: floor-plan.png preview: true previewSrc: fixtures/avatar.png fit: contain width: 160 height: 90 removeLabel: Remove {name} retryLabel: Retry {name} tile-name-hidden
form: tile value: b name: floor-plan.png preview: true previewSrc: fixtures/avatar.png nameHidden: true removeLabel: Remove {name} retryLabel: Retry {name} tile-name-hidden-glyph
form: tile value: a name: brief.pdf nameHidden: true removeLabel: Remove {name} retryLabel: Retry {name} tile-uploading
form: tile value: b name: floor-plan.png status: uploading statusText: Uploading progress: 0.4 preview: true previewSrc: fixtures/avatar.png removeLabel: Remove {name} retryLabel: Retry {name} tile-done
form: tile value: b name: floor-plan.png status: done statusText: Uploaded preview: true previewSrc: fixtures/avatar.png removeLabel: Remove {name} retryLabel: Retry {name} tile-error-retryable
form: tile value: e name: site-photo.png status: error errorText: The connection was lost. retryable: true preview: true previewSrc: fixtures/avatar.png nameHidden: true removeLabel: Remove {name} retryLabel: Retry {name} tile-interactive
form: tile value: b name: floor-plan.png preview: true previewSrc: fixtures/avatar.png interactive: true removeLabel: Remove {name} retryLabel: Retry {name} tile-disabled
form: tile value: e name: site-photo.png status: error errorText: The connection was lost. retryable: true preview: true previewSrc: fixtures/avatar.png removeLabel: Remove {name} retryLabel: Retry {name} disabled: true tile-read-only
form: tile value: b name: floor-plan.png preview: true previewSrc: fixtures/avatar.png href: /files/floor-plan.png readOnly: true tile-long-name
form: tile value: b name: IMG_20260914_103322_HDR_edited_copy_2.jp… sizeText: 12.4 MB preview: true previewSrc: fixtures/avatar.png removeLabel: Remove {name} retryLabel: Retry {name} tile-rtl
form: tile value: e name: site-photo.png status: error errorText: The connection was lost. retryable: true preview: true previewSrc: fixtures/avatar.png removeLabel: Remove {name} retryLabel: Retry {name} direction: rtl Generated from specs/file-item.spec.json, tokens/comp/file-item.tokens.json, design/tokens/
The same three files as rows and as tiles
| Aspect | row (the default) |
tile |
|---|---|---|
| Use it for | A column of files of any kind. | A strip of pictures. |
| Width | The width its place gives. | The box’s: width, never wider than its place. |
| The glyph or the picture | Before the name. With preview, a small square. |
The whole box, width by height. |
| The name | Wraps, and is never cut. | One line beneath the box, cut in the middle so that its ending stays. |
| The second line | Below the name. | Beneath the box, wrapping inside its width. |
| The bar | Beneath the texts. | Along the bottom edge of the box. |
| The actions | At the end: retry, then remove. | In the box, at its top end corner. |
width and height are numbers, read in the tile form alone. Without height the box is as high as the token comp.file-item.tile.media.height says; without width it is square. Choose the proportion of the pictures you have: width="160" height="90" for screenshots, a square for portraits of people. A box is never narrower than its two actions need.
There is no third, compact form. A file on one line is a row with nothing on its second line.
Each part is shown when you give it. Two on-off props, preview and nameHidden, change what is shown. No prop lists the parts to show.
| Part | Shown when |
|---|---|
| The glyph | Always, where no picture is drawn. |
| The picture | preview is set, and there is a picture to draw. |
| The name | Always in a row. In a tile, except when nameHidden is set and a picture is drawn. |
| The format in words | You give formatText. Tanrei has no word for a format. |
| The size | You give sizeText, formatted for the reader’s language. |
| The status in words | You give statusText. |
| The error | status is error and you give errorText. It stands in the status’s place, in every form, and is never cut. |
| The bar | status is uploading. Filled to progress, or indeterminate while progress is not set. |
A done file and a failed file have a mark before the words of their status: a check, and an error mark. The glyph before the name does not change with the status. A person learns one place for each: before the name, what the file is; before the status, how it stands.
nameHidden is for a dense strip of pictures. It hides the name only behind a picture that is drawn. A tile that shows a glyph keeps its name, because a format’s glyph does not tell two files of one format apart. The name stays the item’s name for assistive technology in every case.
There are ten kinds of glyph. Each kind has a list of media types and a list of file extensions. The glyph is chosen from mediaType. Where the media type is empty, is application/octet-stream or is on none of these lists, it is chosen from the extension of name. The kinds are matched in this order. The lists are fixed and the same on every platform, so one file draws one glyph wherever it is shown. video/mp2t decides nothing either: platforms guess it from the ending .ts, which TypeScript files share with video streams, so index.ts draws the code glyph.
The ten kinds
| Kind | Glyph | Media types | Endings |
|---|---|---|---|
picture_as_pdf |
application/pdf |
||
| spreadsheet | table |
text/csv, text/tab-separated-values, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.oasis.opendocument.spreadsheet, application/vnd.apple.numbers, application/x-iwork-numbers-sffnumbers |
xls, xlsx, ods, numbers, csv, tsv |
| presentation | slideshow |
application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.oasis.opendocument.presentation, application/vnd.apple.keynote, application/x-iwork-keynote-sffkey |
ppt, pptx, odp, key |
| archive | folder_zip |
application/zip, application/x-zip-compressed, application/x-tar, application/gzip, application/x-gzip, application/x-bzip2, application/x-xz, application/x-7z-compressed, application/vnd.rar, application/x-rar-compressed |
zip, tar, gz, tgz, bz2, xz, 7z, rar |
| code | code |
text/html, text/css, text/javascript, application/javascript, application/x-javascript, text/typescript, application/typescript, text/jsx, text/tsx, application/json, application/xml, text/xml, text/yaml, text/x-yaml, application/yaml, application/x-yaml, application/x-sh, text/x-sh, text/x-shellscript, text/x-python, text/x-script.python, text/x-ruby, application/x-ruby, text/x-go, text/x-rust, text/x-java, text/x-java-source, text/x-kotlin, text/x-swift, text/x-c, text/x-chdr, text/x-csrc, text/x-c++, text/x-c++src, text/x-csharp, text/x-php, application/x-php, application/x-httpd-php, application/sql, text/x-sql |
html, css, js, mjs, ts, tsx, jsx, json, xml, yaml, yml, sh, py, rb, go, rs, java, kt, swift, c, h, cpp, cs, php, sql |
| image | image |
Every type that begins with image/ |
png, jpg, jpeg, gif, webp, avif, svg, bmp, tif, tiff, heic, heif, ico |
| video | movie |
Every type that begins with video/ |
mp4, mov, m4v, webm, mkv, avi |
| audio | music_note |
Every type that begins with audio/ |
mp3, wav, m4a, aac, ogg, oga, flac, opus |
| document | description |
Every other type that begins with text/, and application/rtf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.oasis.opendocument.text, application/vnd.apple.pages, application/x-iwork-pages-sffpages |
txt, md, rtf, doc, docx, odt, pages |
| other | draft |
— | Everything else, and a name without a dot |
- The media type decides before the ending does. A file whose media type is on a list is that kind whatever its name ends with.
- The file is never opened to learn its format.
- For a format of your own, name a glyph of the icon set with
glyph, or put your own in theiconslot. - Every glyph has one color. A format has no color of its own: a color means one thing wherever it is used, and a format is not a status.
- Say the format in words with
formatTextwhere it matters. The glyph is hidden from assistive technology.
preview draws a picture of the file in the glyph’s place. It has two sources:
previewSrc: the address of a small version of the file that your server made. It is drawn for a file of any format.- The file itself, where
previewSrcis empty and the file is an image. On the web you set thefileproperty to theFile.
fit says how the picture fills its box. cover, the default, fills the box and cuts what does not fit; use it for photographs. contain shows the whole picture on the box’s ground; use it for scans, signatures and diagrams, where the edges matter.
A box of 160 by 90: cover, contain, and the name hidden
What a preview costs:
| The picture comes from | What it costs |
|---|---|
With preview off, the default |
Nothing. A hundred file items cost what their texts cost. |
With previewSrc |
One image request for each item, made by the platform’s own image loading. |
| With the file itself | One address for the file, made by the item when it first comes into or near view, and released when the item leaves the page, when you give it another file or none, and when the preview is no longer asked for. The platform’s image decoder reads the file to draw it. The item reads nothing itself. |
- Only images are drawn from the file. A frame of a video or a page of a document would mean opening and reading the file, which no Tanrei component does. Give
previewSrcfor those. - Until the picture can be drawn, and if it cannot be, the format’s glyph is drawn. There is no spinner and no broken-picture mark, and
statusis not touched: a thumbnail that did not load is not the file’s failure. - The picture has no alternative text. It stands where the glyph stood, and the file is named by
name.
By default the body of a file item takes no focus and answers nothing. The item only holds its actions.
A body that is a button, a link that opens in a new tab, and a read-only file
| Prop | The body is | Use it for |
|---|---|---|
interactive |
A button. It emits click with value. |
Opening the file where the person is: a picture in a viewer of your own, a document in a panel. |
href |
A link to the file. It emits click first; cancel it to take the navigation yourself. |
A file your server already has. With target="new", give targetLabel. |
| Neither | Not pressable. | A file that is only listed. |
The body is the glyph or the picture, and the texts. The actions stand beside it and are never inside it, so a pressable file item has three stops at most: the body, retry, remove.
readOnly draws neither action and keeps no room for them. Use it for the files of a record that is only read. The body can still be pressed.
disabled disables the actions and the body. What the item shows stays as it is and stays readable. A disabled action keeps the ground it has at rest, none in a row and its disc in a tile: only its glyph fades.
A file item has no handle and no order of its own. Reordering belongs to the list: put each file item in an item of a reorderable list. The list draws the handle beside the file item, lets the whole item be dragged and opens its menu of moves from the handle; the file item keeps its glyph, its texts, its bar and its actions.
Files in an order people change: file items in a reorderable list
<tanrei-list reorderable gap="small" label="Attachments" drag-handle-label="Reorder {label}"
move-to-start-label="Move to top" move-before-label="Move up"
move-after-label="Move down" move-to-end-label="Move to bottom"
lifted-text="{label} lifted: {position} of {count}"
moved-text="{label}: {position} of {count}"
cancelled-text="{label} returned to {position} of {count}">
<tanrei-list-item><tanrei-file-item name="cover-letter.pdf" size-text="120 KB" remove-label="Remove {name}"></tanrei-file-item></tanrei-list-item>
…
</tanrei-list>The list moves nothing itself: on move, put the items in the new order. It announces each move and keeps focus on the moved item’s handle; see the list. Name each list item by its file (the file’s name is the content’s text, so nothing more is needed). A file upload arranges its files in the same way.
The file item has no words of its own. Two can be given once for the whole product with setWords(); every other text is a prop of the instance.
| Prop | Key of the product’s words | Without it |
|---|---|---|
removeLabel |
removeLabel |
The remove action is drawn and has no name. A debug build reports it. |
retryLabel |
retryLabel |
The retry action is drawn and has no name. A debug build reports it. |
Each action’s name holds its file’s name: removeLabel and retryLabel are sentences with the place {name}, which the file item fills (Remove {name}). One sentence is enough for every file, and a person hears “Remove brief.pdf, button” — in the order of your language — also where a screen reader lists the buttons of the page. A sentence without {name} names every file’s action alike, and a debug build reports it.
In a right-to-left layout the glyph or the picture stands at the right and the actions at the left, with remove outermost; a bar fills from the right, and a tile’s actions are at its top left corner. A picture is never mirrored. A file name in Latin letters keeps its own order, with its dot and its extension. Of the ten format glyphs only the one for other is mirrored, because the icon set marks it.
- Give
statusTextto a done file anderrorTextto a failed one. A status is never told by a mark or a color alone. - Set
retryableonly for a failure that trying again may cure. A file that is too large will be too large again. - Format the size yourself. Whether a megabyte is 1000 or 1024 kilobytes is your product’s decision.
- Put file items that belong together in a list of your own, a
<ul>with a name, or in a file upload. - Move focus yourself after you remove an item. Alone, a file item cannot know what stands next to it.
- Don’t expect the item to remove itself, to change its status or to move its bar. It draws what you set.
- Don’t use
nameHiddenfor files that are not pictures. Their tiles keep the name anyway. - Don’t put a menu, a checkbox or a caption inside it. Its one slot is the glyph. Further actions on a file stand beside the item.
- Don’t use it as the row of a list people select from or reorder. That is the list’s row.
- Role
group- Labeling
- A group named by the file's name — also in a tile that does not draw it — reading the name, the format, the size and the status or the error in words, a progress bar named by the file while it is sent, and its actions: buttons named retryLabel and removeLabel, each with the file's name in its place ({name}). A pressable body is a button, or a link, named by the file's name and described by the second line. Inside a File upload the file item is an item of the upload's list in the group's place (FUP-25).
- Minimum target
- The hit area meets the minimum target size even when the visual size is smaller. minimum 32px · pointer 44px · spacing 8px · touch 48px
Keyboard
| Keys | Action |
|---|---|
| Tab / Shift+Tab | Moves focus to the body, where the file item is pressable, then to its retry action where it has one, then to its remove action. A file item that is not pressable and is read-only is no stop at all. |
| Enter / Space | On a pressable body: emits click, for the product to open the file. On an action: asks whoever holds the file item to retry or to remove the file. |
| Enter | On a body that is a link: follows it. Space does what it does on any link (FIT-13). |
Notes
- The name is the name in every form (FIT-8, FIT-19): hiding it in a strip of pictures hides it from sight alone, and only behind a picture that is drawn. A picture is no text alternative for a name, and the file item asks for no description of the picture: the picture stands for the file, which has a name. A product that shows the picture as content — to be looked at — shows it in an Image preview, whose images have their own alternative text.
- Status is never color alone (WCAG 1.4.1): a done or failed file has words, and its mark has a shape of its own. The glyph before the name says the format and never the status, so that a person learns one place for each.
- A failure is never hidden (WCAG 3.3.1): errorText is drawn in every form, wraps and is never cut, also beneath a tile whose name is hidden.
- A format has no color. Ten colors for ten kinds would be ten meanings a person must learn, would compete with the two status colors on the same line, and would say nothing to a person who does not tell them apart; the glyph's shape and, where the product has them, the format's words carry it.
- The remove and retry actions are named by one sentence of the product's with the file's name in it, so a person tabbing through a list, or listing the buttons of the page, hears "Remove brief.pdf, button" — in the order of the product's language — without the product writing a sentence per file.
- Nothing is announced by a file item (FIT-19). A component that holds file items — a File upload, a chat composer — has one live region for all of them and says what was added, finished and failed there; a product that arranges file items itself does the same with a region of its own.
- A cut name (FIT-9) is cut for sight alone and keeps its ending. The whole name is the accessible name; on the web it is also the body's or the name's `title` only where the product asks for one, because a tooltip is not reachable by touch and is no substitute for the row form, which never cuts.
- A picture that moves (an animated image) is the platform's to hold still or not (FIT-23). On the web an image element plays it; a product whose strip must not move gives a still `previewSrc`.
- Without script, and until the component is defined, on the web: nothing is drawn, because the name is an attribute. A page that must list files where script does not run writes its own links to them, as it would without Tanrei.
- Compose and React Native: the group is a node with the name as its content description (Compose) or a labelled container (React Native), and the actions keep their own nodes.
Generated from specs/file-item.spec.json, tokens/sys/measure.tokens.json
- Alone, a file item is a group named by the file’s name. In a file upload it is an item of the upload’s list.
- A pressable body is one button or one link, named by the file’s name and described by the second line. It comes before the actions in the focus order. A link is activated by Enter and not by Space, and a link that opens in a new tab says
targetLabelafter its name. - The actions are buttons named by
removeLabelandretryLabel, with the file’s name in the place{name}. Retry comes before remove. - The bar is a progress bar named by the file. An error is the text of the second line, read in the status’s place.
- The glyph, the picture and the status mark are hidden from assistive technology. The words say what they show.
- A file item announces nothing. Whoever holds many of them has one live region for them all, as the file upload has.
- The whole name always reaches assistive technology, also where a tile cuts it and where
nameHiddenhides it. - At 200% text a row wraps and loses nothing. Where the glyph and the actions would leave the name less than half the row, the actions move to a line of their own below the texts, at the row’s end, and the name takes the whole width. A tile’s box keeps its size and its texts grow beneath it.
- In forced colors the box and a tile’s action discs keep an edge, and the bar stays visible. A picture is left as it is.
- Color changes between states are animated over a short time, and happen at once where motion is reduced.
Known gaps on the web:
- A picture that moves, such as an animated GIF, is played by the browser’s image element. The file item cannot hold it still. Give a still version of the picture as
previewSrcwhere that matters. - Before the element is defined nothing is drawn, because the name is an attribute and not text in the document. A page that must list files where script does not run writes its own links.
- Give
name, always. - Give
removeLabelandretryLabel, on the element or once withsetWords(). - Say every status and every failure in words:
statusTextanderrorText. - With
target="new", givetargetLabel. - Give a group of file items a list and a name of your own, and announce what was added, finished or failed in one live region of your own. A file upload does both for you.
- After removing an item, move focus to the next file’s remove action, or to what added the files.