Attachment

ui-attachment shows a file attached to a message or a form, with a small preview area, the file name, its size and room for actions such as remove. The name is a link with the download attribute, so clicking it downloads the file from href under that name. Long names are truncated with an ellipsis.

The preview goes in a ui-attachment-media child and the buttons in a ui-attachment-actions child. Both place themselves before and after the name, whatever their position in the source.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Attachment demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/attachment.html"></import-element>

  <ui-attachment name="quarterly-report.pdf" size="1.2 MB" href="data:text/plain,Quarterly%20report">
    <ui-attachment-media>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M14 3H6v18h12V7Z" /><path d="M14 3v4h4" /></svg>
    </ui-attachment-media>
  </ui-attachment>
</body>
</html>

Examples

With actions

Put buttons in ui-attachment-actions. Give icon buttons an aria-label that says what they do to which file.

Previewindex.htmlattachment-list-demo.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Attachment demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-medium {
      width: min(100%, calc(var(--spacing) * 120));
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/attachment.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>

  <import-element from="./attachment-list-demo.html"></import-element>
  <div class="demo-medium"><attachment-list-demo></attachment-list-demo></div>
</body>
</html>
<custom-element name="attachment-list-demo">
  <script>
    const files = useState({
      list: [
        { id: 1, name: "design-review.fig", size: "4.8 MB", remove: "Remove design-review.fig" },
        { id: 2, name: "meeting-notes.md", size: "6 KB", remove: "Remove meeting-notes.md" },
      ],
    });

    function remove(file) {
      files.list.splice(files.list.indexOf(file), 1);
    }
  </script>

  <style>
    ul {
      display: flex;
      flex-wrap: wrap;
      gap: calc(var(--spacing) * 2);
      margin: 0;
      padding: 0;
      list-style: none;
    }
    ui-button svg {
      width: calc(var(--spacing) * 4);
      height: calc(var(--spacing) * 4);
    }
  </style>

  <ul>
    <li repeat="#files.list" as="file" key="id">
      <ui-attachment _name="#file.name" _size="#file.size">
        <ui-attachment-media>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M14 3H6v18h12V7Z" /><path d="M14 3v4h4" /></svg>
        </ui-attachment-media>
        <ui-attachment-actions>
          <ui-button variant="ghost" size="icon-xs" _aria-label="#file.remove" onclick="remove(file)">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
          </ui-button>
        </ui-attachment-actions>
      </ui-attachment>
    </li>
  </ul>
</custom-element>

Image preview

ui-attachment-media is a rounded square with hidden overflow, and an <img> inside it covers the square, so it works as a thumbnail.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Attachment demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/attachment.html"></import-element>

  <ui-attachment name="sunset.svg" size="1 KB" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23f59e0b'/%3E%3Ccircle cx='16' cy='20' r='8' fill='%23f97316'/%3E%3C/svg%3E">
    <ui-attachment-media>
      <img alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23f59e0b'/%3E%3Ccircle cx='16' cy='20' r='8' fill='%23f97316'/%3E%3C/svg%3E">
    </ui-attachment-media>
  </ui-attachment>
</body>
</html>

In a message

Previewindex.html
ME Here is the contract.
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Attachment demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-medium {
      width: min(100%, calc(var(--spacing) * 120));
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/attachment.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/avatar.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/bubble.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/message.html"></import-element>

  <div class="demo-medium">
    <ui-message align="end">
      <ui-message-avatar>
        <ui-avatar><ui-avatar-fallback>ME</ui-avatar-fallback></ui-avatar>
      </ui-message-avatar>
      <ui-bubble align="end">Here is the contract.</ui-bubble>
      <ui-attachment name="contract-final.pdf" size="320 KB" href="data:text/plain,Contract">
        <ui-attachment-media>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M14 3H6v18h12V7Z" /><path d="M14 3v4h4" /></svg>
        </ui-attachment-media>
      </ui-attachment>
    </ui-message>
  </div>
</body>
</html>

API

Parts

Part Slot Renders
ui-attachment (root) The chip with the file name link and size.
ui-attachment-media media A muted rounded square before the name for a file icon (<svg>, 16px) or a thumbnail <img>.
ui-attachment-actions actions A row of buttons after the name, such as remove or open.

The parts set their own slot, so they go directly inside ui-attachment with no attributes. The default slot of each part takes any content.

Attributes

Attribute Type Default Description
name string File name. Shown as the link text and used as the download file name.
size string Human-readable size, such as 1.2 MB. Hidden when empty.
href string URL of the file. Without it the name is not a link.

CSS parts

Part Description
content The column with the name and size.
title The file name link.

Accessibility

The file name is a native link with download, so it is focusable and announced as a link when href is set. The attachment shows a focus ring when anything inside it has focus. Mark icons in ui-attachment-media with aria-hidden="true" and give thumbnails an empty alt when the file name already says what they are.

Edit this page on GitHub · View as Markdown