Styling contract
The headless drop zone only exposes host-level state attributes. Shape, colors, drag affordances, and rejected-state presentation stay fully owner-controlled.
Host state hooks
| Hook | When it appears | Use it for |
|---|---|---|
data-file-upload | Always present on the host. | Base selector for scoping drop-zone styles. |
data-dragging | While a drag gesture is active over the host. | Highlight the active drop target. |
data-disabled | Whenever the directive is disabled. | Dim or lock the zone for the passive disabled state. |
data-rejected | While the drag/drop state is rejected (when surfaced). | Optional negative feedback styling. |
Host selectors
css
[data-file-upload]
[data-dragging]
[data-disabled]
[data-rejected]
CSS starter
Start with a dashed drop target, then react to data-dragging and data-disabled for hover and locked affordances.
dropzone.css
css
.docs-dropzone {
border: 2px dashed var(--tng-semantic-border-strong);
border-radius: 0.85rem;
padding: 1.5rem;
text-align: center;
transition: background 0.15s ease, border-color 0.15s ease;
}
.docs-dropzone[data-dragging] {
background: color-mix(in srgb, var(--tng-semantic-accent-brand) 10%, transparent);
border-color: var(--tng-semantic-accent-brand);
}
.docs-dropzone[data-disabled] {
cursor: not-allowed;
opacity: 0.55;
}
Accessibility guidance
The primitive does not render its own status region. Mirror filesSelected and filesRejected results into your own aria-live node so the outcome of a drop is announced.
Owner-managed status region
html
<div
tngFileUpload
class="docs-dropzone"
[accept]="'image/*'"
(filesSelected)="onFilesSelected($event)"
(filesRejected)="onFilesRejected($event)"
></div>
<p aria-live="polite">{{ statusMessage() }}</p>