Three files dropped into a composer upload side by side under progress rings with their own percent, then turn into context chips with a token size as the count reaches 3 of 3 and Send lights up.
Also known as: file upload progress ring, chat attachment upload, attach files to prompt.
<div class="aia-composer-attachment-upload" role="status" aria-label="Uploading 3 attachments">
<ul class="aia-composer-attachment-upload-files">
<li style="--s: 0.2; --u: 2.4"><span class="aia-composer-attachment-upload-type">SQL</span><span class="aia-composer-attachment-upload-meta"><b>schema.sql</b><span class="aia-composer-attachment-upload-sub"><i class="aia-composer-attachment-upload-pct"></i><i>1.2k tokens</i></span></span></li>
<li style="--s: 0.5; --u: 5.6"><span class="aia-composer-attachment-upload-type">LOG</span><span class="aia-composer-attachment-upload-meta"><b>error.log</b><span class="aia-composer-attachment-upload-sub"><i class="aia-composer-attachment-upload-pct"></i><i>3.1k tokens</i></span></span></li>
<li style="--s: 0.9; --u: 7.4"><span class="aia-composer-attachment-upload-type aia-composer-attachment-upload-img"></span><span class="aia-composer-attachment-upload-meta"><b>dashboard.png</b><span class="aia-composer-attachment-upload-sub"><i class="aia-composer-attachment-upload-pct"></i><i>Image</i></span></span></li>
</ul>
<p class="aia-composer-attachment-upload-text">Why does the dashboard 500 after the schema change?</p>
<div class="aia-composer-attachment-upload-bar">
<span class="aia-composer-attachment-upload-clip" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m20 11.5-8.2 8.2a5 5 0 0 1-7-7l8.6-8.6a3.4 3.4 0 0 1 4.8 4.8l-8.6 8.6a1.7 1.7 0 0 1-2.4-2.4l7.9-7.9"/></svg></span>
<span class="aia-composer-attachment-upload-count" aria-hidden="true"></span>
<i class="aia-composer-attachment-upload-send" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></i>
</div>
</div>@property --aia-composer-attachment-upload-t {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --aia-composer-attachment-upload-pc {
syntax: "<integer>";
inherits: false;
initial-value: 0;
}
@property --aia-composer-attachment-upload-n {
syntax: "<integer>";
inherits: false;
initial-value: 0;
}
.aia-composer-attachment-upload {
--c: var(--aia-color, #7c5cff);
font-size: var(--aia-size, 16px);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.4;
position: relative;
box-sizing: border-box;
padding: 0.9em 1em;
border-radius: 0.9em;
background: color-mix(in srgb, currentColor 3%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 11%, transparent), 0 0.1em 0.2em color-mix(in srgb, currentColor 4%, transparent), 0 0.9em 2em -1.1em rgb(0 0 0 / 0.3);
--d: 8s;
--t: var(--aia-composer-attachment-upload-t);
--ok: color-mix(in srgb, #16a34a 78%, currentColor);
--all: clamp(0, (var(--t) - 8.3) * 6, 1);
width: 25em;
padding: 0.6em 0.75em 0.5em;
border-radius: 1em;
animation:
aia-composer-attachment-upload-clock var(--d) linear infinite,
aia-composer-attachment-upload-fade var(--d) linear infinite;
}
.aia-composer-attachment-upload-files {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.4em;
margin: 0;
padding: 0;
list-style: none;
}
.aia-composer-attachment-upload-files li {
--p: clamp(0, (var(--t) - var(--s)) / var(--u), 1);
--ok-p: clamp(0, (var(--t) - var(--s) - var(--u) - 0.1) * 5, 1);
display: flex;
align-items: center;
gap: 0.4em;
min-width: 0;
padding: 0.3em;
border-radius: 0.65em;
background: color-mix(in srgb, var(--c) calc(var(--ok-p) * 9%), color-mix(in srgb, currentColor 5%, transparent));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) calc(var(--ok-p) * 32%), color-mix(in srgb, currentColor 10%, transparent));
}
.aia-composer-attachment-upload-type {
position: relative;
flex: none;
display: grid;
place-items: center;
width: 3.3em;
height: 3.3em;
overflow: hidden;
border-radius: 0.75em;
background: var(--c);
color: #fff;
font: 700 0.62em ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
}
.aia-composer-attachment-upload-files li:nth-child(2) .aia-composer-attachment-upload-type { background: #52525b; }
.aia-composer-attachment-upload-type.aia-composer-attachment-upload-img {
background:
radial-gradient(circle at 70% 30%, #fde68a 0 12%, transparent 13%),
linear-gradient(160deg, transparent 55%, #0f766e 56%),
linear-gradient(200deg, transparent 60%, #14b8a6 61%),
linear-gradient(#7dd3fc, #bae6fd);
}
/* the scrim and ring sit on the badge until the upload lands */
.aia-composer-attachment-upload-type::before {
content: "";
position: absolute;
inset: 0;
background: rgb(0 0 0 / 0.5);
opacity: calc(1 - var(--ok-p));
}
.aia-composer-attachment-upload-type::after {
content: "";
position: absolute;
inset: 22%;
border-radius: 50%;
background: conic-gradient(#fff calc(var(--p) * 1turn), rgb(255 255 255 / 0.28) 0);
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 0.42em), #000 calc(100% - 0.4em));
mask: radial-gradient(farthest-side, transparent calc(100% - 0.42em), #000 calc(100% - 0.4em));
opacity: calc(1 - var(--ok-p));
transform: scale(calc(1 + var(--ok-p) * 0.5));
}
.aia-composer-attachment-upload-meta {
display: grid;
min-width: 0;
line-height: 1.25;
}
.aia-composer-attachment-upload-meta b {
overflow: hidden;
font-size: 0.72em;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.aia-composer-attachment-upload-sub {
display: grid;
font-size: 0.64em;
}
.aia-composer-attachment-upload-sub i {
grid-area: 1 / 1;
font-style: normal;
white-space: nowrap;
}
.aia-composer-attachment-upload-pct {
color: color-mix(in srgb, currentColor 55%, transparent);
font-variant-numeric: tabular-nums;
opacity: calc(1 - var(--ok-p));
--aia-composer-attachment-upload-pc: calc(var(--p) * 100 - 0.5);
counter-reset: pc var(--aia-composer-attachment-upload-pc);
}
.aia-composer-attachment-upload-pct::before { content: counter(pc) "%"; }
.aia-composer-attachment-upload-sub i:last-child {
color: color-mix(in srgb, var(--c) 72%, currentColor);
font-weight: 500;
opacity: var(--ok-p);
transform: translateY(calc((1 - var(--ok-p)) * 0.5em));
}
.aia-composer-attachment-upload-text {
margin: 0.6em 0.2em 0.35em;
font-size: 0.9em;
}
.aia-composer-attachment-upload-bar {
display: flex;
align-items: center;
gap: 0.5em;
}
.aia-composer-attachment-upload-clip {
display: grid;
place-items: center;
width: 1.7em;
height: 1.7em;
color: color-mix(in srgb, currentColor 50%, transparent);
}
.aia-composer-attachment-upload-clip svg {
width: 1em;
height: 1em;
}
.aia-composer-attachment-upload-count {
color: color-mix(in srgb, currentColor 55%, transparent);
font-size: 0.74em;
font-variant-numeric: tabular-nums;
--aia-composer-attachment-upload-n: calc(clamp(0, (var(--t) - 2.7) * 9, 1) + clamp(0, (var(--t) - 6.2) * 9, 1) + clamp(0, (var(--t) - 8.4) * 9, 1) - 0.5);
counter-reset: n var(--aia-composer-attachment-upload-n);
}
.aia-composer-attachment-upload-count::before { content: counter(n) " of 3 in context"; }
.aia-composer-attachment-upload-send {
display: grid;
place-items: center;
width: 1.9em;
height: 1.9em;
margin-left: auto;
border-radius: 50%;
background: color-mix(in srgb, var(--c) calc(var(--all) * 100%), color-mix(in srgb, currentColor 14%, transparent));
color: color-mix(in srgb, #fff calc(var(--all) * 100%), color-mix(in srgb, currentColor 45%, transparent));
box-shadow: 0 0 0 calc(var(--all) * 0.3em) color-mix(in srgb, var(--c) 18%, transparent);
}
.aia-composer-attachment-upload-send svg {
width: 1.05em;
height: 1.05em;
stroke-width: 2.4;
}
@keyframes aia-composer-attachment-upload-clock {
0% { --aia-composer-attachment-upload-t: 0; }
12% { --aia-composer-attachment-upload-t: 1.6; }
20% { --aia-composer-attachment-upload-t: 2.2; }
34% { --aia-composer-attachment-upload-t: 4.6; }
44% { --aia-composer-attachment-upload-t: 5.4; }
62%, 97% { --aia-composer-attachment-upload-t: 9; }
98%, 100% { --aia-composer-attachment-upload-t: 0; }
}
@keyframes aia-composer-attachment-upload-fade {
0%, 93% { opacity: 1; }
97%, 99% { opacity: 0; }
100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.aia-composer-attachment-upload {
animation: none;
--aia-composer-attachment-upload-t: 9;
}
}
@media (prefers-reduced-motion: reduce) {
.aia-composer-attachment-upload, .aia-composer-attachment-upload *, .aia-composer-attachment-upload::before, .aia-composer-attachment-upload::after, .aia-composer-attachment-upload *::before, .aia-composer-attachment-upload *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--aia-color on the element or a parent.
Currently previewing #3b82f6.--aia-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.