Skip to content

feat: propagate <f-template> host attributes onto rendered host element#7521

Merged
janechu merged 3 commits into
mainfrom
users/janechu/feat/host-attribute-propagation
May 20, 2026
Merged

feat: propagate <f-template> host attributes onto rendered host element#7521
janechu merged 3 commits into
mainfrom
users/janechu/feat/host-attribute-propagation

Conversation

@janechu
Copy link
Copy Markdown
Collaborator

@janechu janechu commented May 19, 2026

Pull Request

📖 Description

Adds support for propagating host attributes declared on the inner <template> of an <f-template> definition onto the rendered host element opening tag during SSR.

Today the build-time renderer drops every attribute on the source <template>, so authors cannot pre-declare host attributes (e.g. tabindex, role, aria-*) on a component's declarative template. After this change, the following template:

<f-template name="host-autofocus">
    <template tabindex="0">
        <span>{{text}}</span>
    </template>
</f-template>

Now renders as <host-autofocus autofocus tabindex="0">…</host-autofocus> when the author writes <host-autofocus autofocus></host-autofocus> — both the author-provided autofocus and the template-provided tabindex="0" survive SSR.

Resolution rules:

  • Static name="value", dynamic name="{{expr}}", and boolean ?name="{{expr}}" bindings on the template's <template> are propagated.
  • Author-provided host attributes win on conflict (dedupe on lowercased name; ?name dedupes against bare name).
  • Client-only attribute prefixes are skipped: @event, :property, f-ref, f-slotted, f-children.
  • {{expr}} resolves against the same state used to render the shadow root; non-primitive / null values are stripped.

Breaking change (prerelease): Locator::add_template_with_shadowroot_attrs was renamed and widened to add_template_with_attrs(name, content, shadowroot_attrs, host_attrs). Locator::from_template_definitions is now pub and accepts a 3-tuple metadata value (content, shadowroot_attrs, host_attrs). The WASM/JSON shape templatesMap[name] now includes a hostAttributes array. No external callers existed inside the monorepo.

👩‍💻 Reviewer Notes

  • crates/microsoft-fast-build/src/directive.rs is where the actual splice happens. merge_template_host_attrs formats and joins filtered attrs before the rightmost > of the host opening tag.
  • The skip list is intentionally hand-rolled (no shared constant with the existing client-side dispatch in fast-html); see the is_client_only_attr helper for the canonical list.
  • The host-bindings fixture (packages/fast-html/test/fixtures/host-bindings) gained an intentional autofocus usage; a biome-ignore lint/a11y/noAutofocus HTML comment documents the rationale and survives npm run build:fixtures regeneration of index.html.

📑 Test Plan

  • 16 new Rust unit/integration tests in crates/microsoft-fast-build (tests/host_attributes.rs + locator.rs unit tests + 2 WASM tests) — all 87 cargo tests pass.
  • 1 new Playwright assertion in packages/fast-html/test/fixtures/host-bindings/host-bindings.spec.ts verifying both autofocus (author) and tabindex="0" (propagated) survive SSR + hydration.
  • Full repo acceptance: npm run build ✓, npm run test ✓ (301/301 fast-html chromium, no regressions in other fixtures), npm run biome:check ✓, npm run checkchange ✓.

✅ Checklist

General

  • I have included a change request file using $ npm run change
  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

Static, {{expr}}, and ?name="{{expr}}" attributes declared on the inner
<template> of an <f-template> now propagate onto the rendered host
element opening tag during SSR. Author-provided host attributes win on
conflict. Client-only attribute prefixes (@, :, f-ref, f-slotted,
f-children) are skipped.

* locator.rs: extract_template_content returns (content, host_attrs);
  add_template_with_attrs(name, content, shadowroot_attrs, host_attrs)
  replaces the prior shadowroot-only helper (breaking change in
  prerelease).
* directive.rs: render_custom_element splices template host attrs into
  the host opening tag for both entry and nested paths.
* wasm.rs / packages/fast-build/bin/fast.js: hostAttributes flow
  through templatesMap and parse_f_templates JSON.
* Adds host-autofocus fixture component and Playwright assertion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu marked this pull request as ready for review May 19, 2026 02:57
… fixture

Biome's HTML formatter collapses <template> elements with short
content onto a single line, which causes the WASM SSR renderer in
@microsoft/fast-build to drop the leading fe-b$$start binding
marker for the first-position text node inside the host element.
Without the start marker, client hydration cannot bound the binding
range and clears the rendered content.

Restore the original multi-line <template> layout in templates.html
and add biome-ignore-all comments at the top of templates.html and
entry.html so future biome:fix runs preserve the layout. Index.html
is regenerated from the restored sources.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mohamedmansour
Copy link
Copy Markdown
Contributor

This doesn't make sense why we want this magic:

<f-template name="host-autofocus">
    <template tabindex="0">
        <span>{{text}}</span>
    </template>
</f-template>

to propagate to:

<host-autofocus tabindex="0">

Why do we want component devs to propagate attributes? This doesn't follow the w3c spec, if they want tabindex they should do that in JS to enforce it, not automatically bring all the attributes the component itself. This would become very messy where now we have to add an allow list of attributes NOT to propagte.

So I am not fan doing this approach.

@janechu
Copy link
Copy Markdown
Collaborator Author

janechu commented May 19, 2026

This doesn't make sense why we want this magic:

<f-template name="host-autofocus">
    <template tabindex="0">
        <span>{{text}}</span>
    </template>
</f-template>

to propagate to:

<host-autofocus tabindex="0">

Why do we want component devs to propagate attributes? This doesn't follow the w3c spec, if they want tabindex they should do that in JS to enforce it, not automatically bring all the attributes the component itself. This would become very messy where now we have to add an allow list of attributes NOT to propagte.

So I am not fan doing this approach.

Commented in the other implementation, this is basically our current method on CSR (Client Side Rendering), we take static attributes from the template and reflect them onto the host. This is supported in FAST v2 out of the box. We're mirroring that ability, in the case that an attribute should not propagate that is up to the component author. They can choose to set an attribute on the template, and in this case the DSD will have it since this is a declarative solution, or they can choose to set it in the connectedCallback.

The specific use case in which it is very awkward on the platform is use of autofocus. Right now a custom element must have tabindex="0" for that to work, otherwise when the page is initially loaded it won't. It is not preferrable IMO for an item that should be tabbed to out of the box and it can be assumed will potentially have autofocus on it that a developer must know that it also needs tabindex. They should just be able to set autofocus.

Component authors can choose not to use it, but since it is already an aspect of FASTElement I'm merely reaching parity with our test renderer. We are considering a new approach for v4.

@janechu janechu merged commit 89c18a1 into main May 20, 2026
14 checks passed
@janechu janechu deleted the users/janechu/feat/host-attribute-propagation branch May 20, 2026 21:44
janechu added a commit that referenced this pull request May 20, 2026
…ge (#7522)

# Pull Request

## 📖 Description

Adds a new **"Host Attribute Propagation"** section to the 3.x declarative server-rendering documentation page (`sites/website/src/docs/3.x/declarative-templates/server-rendering.md`) describing how attributes declared on the inner `<template>` of an `<f-template>` propagate onto the rendered host element opening tag during SSR.

The new section covers:

- The concept and a worked example (a `primary-button` component with `tabindex`, `role`, and `class` declared on its template).
- The three supported binding forms: static `name="value"`, dynamic `name="{{expression}}"`, and boolean `?name="{{expression}}"`, including which values get stripped.
- The client-only attribute skip list (`@event`, `:property`, `f-ref`, `f-slotted`, `f-children`).
- The "author wins on conflict" dedupe rule (including how `?name` dedupes against the bare `name`).

### 🎫 Issues

This documents the build-time renderer behavior implemented on `main` in [#7521](#7521). The feature will be picked up by the next `main` → `releases/fast-element-v3` sync; this docs PR can land independently so that the website reflects the upcoming behavior.

## 👩‍💻 Reviewer Notes

- Docs-only change — no source, test, or build configuration is touched.
- The new section is placed between **State Propagation** and **Using `@microsoft/fast-build`**, keeping the conceptual flow (rendering contract → hydration flow → state in → host attrs out → tooling).
- Markdown is wrapped inside the existing `{% raw %}` Nunjucks block so that `{{ }}` examples render literally on the site.

## 📑 Test Plan

- No test changes are required. Verified the markdown edits render correctly by inspection (no broken table syntax, no unescaped Nunjucks/Liquid braces outside `{% raw %}`).
- Suggested smoke test for reviewer: `npm run build -w @microsoft/fast-site` (or the website's local dev preview) and open the rendered `server-rendering` page to confirm the new section displays as expected.

## ✅ Checklist

### General

- [ ] I have included a change request file using `$ npm run change`
- [ ] I have added tests for my changes.
- [ ] I have tested my changes.
- [x] I have updated the project documentation to reflect my changes.
- [x] I have read the [CONTRIBUTING](https://github.com/microsoft/fast/blob/main/CONTRIBUTING.md) documentation and followed the [standards](https://github.com/microsoft/fast/blob/main/CODE_OF_CONDUCT.md#our-standards) for this project.

## ⏭ Next Steps

When the next `main` → `releases/fast-element-v3` sync lands, this docs page will accurately describe the behavior shipping in the v3 build pipeline. No further docs work is anticipated for this feature on the v3 track.
@janechu janechu mentioned this pull request May 27, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants