Drupal Integration Best Practices
apps/docs/src/content/docs/drupal/best-practices Click to copy apps/docs/src/content/docs/drupal/best-practices Comprehensive guidance for integrating HELiX web components into Drupal 10/11 themes and modules. This guide covers CDN loading, Twig patterns, Drupal behaviors, Form API integration, and performance optimization for enterprise healthcare applications.
Overview
Section titled “Overview”HELiX web components are designed to integrate seamlessly with Drupal CMS without requiring custom modules or complex build processes. Components work in standard Twig templates, participate in native form submission, and respond to Drupal’s AJAX API.
This guide establishes best practices for:
- Loading components via CDN or local installation
- Writing property-driven and slot-driven Twig templates
- Initializing components with Drupal.behaviors
- Handling custom events in Drupal context
- Optimizing performance for component-heavy pages
- Ensuring accessibility in Drupal workflows
Loading Strategies
Section titled “Loading Strategies”CDN Loading (Recommended for Production)
Section titled “CDN Loading (Recommended for Production)”Load HELiX components from a CDN for automatic caching and global distribution.
Add to your_theme.libraries.yml:
# CDN approach with version pinninghelix-components: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/index.js: type: external attributes: type: module version: 3.9.0 header: trueAttach to your theme globally:
libraries: - your_theme/helix-componentsOr attach conditionally in templates:
{{ attach_library('your_theme/helix-components') }}Local Installation (For Development or Private Hosting)
Section titled “Local Installation (For Development or Private Hosting)”Install HELiX via npm and serve from Drupal’s libraries directory.
Install via npm:
npm install @helixui/libraryCopy to Drupal libraries directory:
cp -r node_modules/@helixui/library/dist /path/to/drupal/libraries/helix/Add to your_theme.libraries.yml:
helix-components-local: js: /libraries/helix/dist/index.js: type: file attributes: type: module version: VERSION header: truePer-Component Loading (Tree-Shaking)
Section titled “Per-Component Loading (Tree-Shaking)”For performance-critical pages, load only the components you use.
helix-button: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/components/hx-button/index.js: type: external attributes: type: module version: 3.9.0
helix-card: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/components/hx-card/index.js: type: external attributes: type: module version: 3.9.0Attach only what you need:
{{ attach_library('your_theme/helix-button') }}{{ attach_library('your_theme/helix-card') }}Twig Template Patterns
Section titled “Twig Template Patterns”Property-Driven Pattern
Section titled “Property-Driven Pattern”Use component properties for simple, data-driven templates. This is the recommended approach for most use cases.
When to use:
- Simple components with few configuration options
- Data-driven content (node fields, user properties)
- When you don’t need complex HTML inside slots
Example:
<hx-card variant="{{ node.field_card_variant.value|default('default') }}" elevation="{{ node.field_elevation.value|default('flat') }}" {% if node.url %}hx-href="{{ node.url }}"{% endif %}> <span slot="heading">{{ node.label }}</span> {{ content.body }}</hx-card>Slot-Driven Pattern
Section titled “Slot-Driven Pattern”Use slots when you need to project complex HTML, multiple elements, or rich content.
When to use:
- Complex markup inside component sections
- Multiple elements in a single slot
- Rich text fields with embedded media
- Custom layouts or nested components
Example:
<hx-card variant="featured"> <div slot="image"> {{ content.field_image }} </div>
<div slot="heading"> <h3>{{ node.label }}</h3> {% if node.field_badge %} <hx-badge variant="primary">{{ node.field_badge.value }}</hx-badge> {% endif %} </div>
<div class="card-content"> {{ content.body }} {{ content.field_tags }} </div>
<div slot="footer"> <small>{{ node.created.value|date('F j, Y') }}</small> <hx-button hx-size="sm">View Details</hx-button> </div></hx-card>Drupal Behaviors Pattern
Section titled “Drupal Behaviors Pattern”Drupal.behaviors is the standard mechanism for initializing JavaScript in Drupal. Use it to set up event listeners, enhance components, and respond to AJAX updates.
Basic Behavior Template
Section titled “Basic Behavior Template”/** * @file * HELiX components Drupal behavior. */
(function (Drupal, once) { 'use strict';
/** * Initialize HELiX components. */ Drupal.behaviors.helixComponents = { attach: function (context, settings) { // Use once() to prevent double-initialization. // Scope to interactive cards — hx-card only emits hx-click in // the hx-href variant, and combining hx-href with slot="actions" // is an ARIA anti-pattern (nested interactive). once('helix-init', 'hx-card[hx-href]', context).forEach((card) => { card.addEventListener('hx-click', (e) => { console.log('Card activated:', e.detail); }); });
// Initialize other components once('helix-select', 'hx-select', context).forEach((select) => { select.addEventListener('hx-change', (e) => { console.log('Select changed:', e.detail); }); }); },
detach: function (context, settings, trigger) { // Clean up on unload if (trigger === 'unload') { context.querySelectorAll('hx-card').forEach((card) => { card.removeEventListener('hx-click', () => {}); }); } }, };})(Drupal, once);Multi-Component Behavior
Section titled “Multi-Component Behavior”Handle multiple components in a single behavior file:
(function (Drupal, once) { Drupal.behaviors.helixAll = { attach: function (context) { // Cards once('hx-card', 'hx-card[hx-href]', context).forEach((card) => { card.addEventListener('hx-click', handleCardClick); });
// Selects once('hx-select', 'hx-select', context).forEach((select) => { select.addEventListener('hx-change', handleSelectChange); });
// Forms once('hx-form', 'hx-form', context).forEach((form) => { form.addEventListener('hx-submit', handleFormSubmit); form.addEventListener('hx-invalid', handleFormInvalid); });
// Buttons once('hx-button', 'hx-button[data-ajax]', context).forEach((button) => { button.addEventListener('click', handleAjaxButton); }); }, };
function handleCardClick(e) { const { url } = e.detail; window.location.href = url; }
function handleSelectChange(e) { const { value } = e.detail; const select = e.target; const name = select.getAttribute('name');
console.log(`${name} changed to: ${value}`);
// Trigger dependent field updates if (name === 'department') { updateSpecialtyOptions(value); } }
function handleFormSubmit(e) { e.preventDefault(); const { formData, values } = e.detail;
fetch(e.target.getAttribute('action'), { method: 'POST', body: formData, }) .then((response) => response.json()) .then((data) => { if (data.success) { window.location.href = data.redirect; } }); }
function handleFormInvalid(e) { // hx-form's hx-invalid detail is { errors: Array<{ name, message }> }. // Project each error back onto its field by name. const form = e.currentTarget; for (const { name, message } of e.detail.errors) { const field = form.querySelector(`[name="${name}"]`); if (field) field.setAttribute('error', message); } const first = e.detail.errors[0]; if (first) { const target = form.querySelector(`[name="${first.name}"]`); target?.scrollIntoView({ behavior: 'smooth', block: 'center' }); target?.focus?.(); } }
function handleAjaxButton(e) { const button = e.currentTarget; const url = button.dataset.ajaxUrl;
// Trigger Drupal AJAX const ajax = Drupal.ajax({ url: url }); ajax.execute(); }
function updateSpecialtyOptions(departmentId) { const specialtySelect = document.querySelector('hx-select[name="specialty"]'); if (!specialtySelect) return;
fetch(`/api/specialties/${departmentId}`) .then((response) => response.json()) .then((data) => { specialtySelect.innerHTML = ''; data.forEach((specialty) => { const option = document.createElement('option'); option.value = specialty.id; option.textContent = specialty.name; specialtySelect.appendChild(option); }); }); }})(Drupal, once);Form API Integration
Section titled “Form API Integration”Using HELiX Components in Drupal Forms
Section titled “Using HELiX Components in Drupal Forms”HELiX components are autonomous custom elements, not customized built-ins — is="hx-*" on a
native <input>/<select>/<button> will not upgrade. Render the HELiX tags from a #theme
callback (or directly in a Twig override) so the markup emits <hx-text-input>, <hx-select>, and
<hx-button> instead of native controls.
function mymodule_patient_intake_form($form, &$form_state) { $form['#attached']['library'][] = 'your_theme/helix-components';
// Text input — #theme rewraps as <hx-text-input> $form['patient_name'] = [ '#type' => 'textfield', '#title' => t('Patient Name'), '#required' => TRUE, '#theme' => 'hx_text_input', '#placeholder' => 'Last, First MI', ];
// Select — #theme rewraps as <hx-select> $form['department'] = [ '#type' => 'select', '#title' => t('Department'), '#options' => [ 'cardiology' => 'Cardiology', 'neurology' => 'Neurology', 'oncology' => 'Oncology', ], '#required' => TRUE, '#theme' => 'hx_select', ];
// Submit button — #theme rewraps as <hx-button variant="primary"> $form['actions']['submit'] = [ '#type' => 'submit', '#value' => t('Submit'), '#theme' => 'hx_button', '#hx_variant' => 'primary', ];
return $form;}AJAX Integration
Section titled “AJAX Integration”Use custom events with Drupal’s AJAX API:
$form['department'] = [ '#type' => 'select', '#title' => t('Department'), '#options' => $department_options, '#ajax' => [ 'callback' => '::updateSpecialty', 'wrapper' => 'specialty-wrapper', 'event' => 'hx-change', // Use custom event ], '#attributes' => [ 'is' => 'hx-select', ],];Performance Optimization
Section titled “Performance Optimization”Lazy Loading Components
Section titled “Lazy Loading Components”Load components only when they enter the viewport:
(function (Drupal, once) { Drupal.behaviors.helixLazy = { attach: function (context) { if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const element = entry.target; element.classList.add('loaded'); observer.unobserve(element); } }); });
once('lazy-load', '[data-lazy]', context).forEach((el) => { observer.observe(el); }); } }, };})(Drupal, once);BigPipe Integration
Section titled “BigPipe Integration”Use Drupal’s BigPipe to progressively load component-heavy blocks:
$build['patient_cards'] = [ '#lazy_builder' => ['mymodule.lazy_builder:renderPatientCards', []], '#create_placeholder' => TRUE,];Render Caching
Section titled “Render Caching”Cache component-rendered blocks to reduce server-side rendering time:
$build['facility_cards'] = [ '#markup' => $this->renderFacilityCards(), '#cache' => [ 'keys' => ['facility_cards'], 'contexts' => ['user'], 'max-age' => 3600, ],];Accessibility in Drupal Context
Section titled “Accessibility in Drupal Context”Form Validation and Error Handling
Section titled “Form Validation and Error Handling”Ensure validation errors are announced to screen readers:
function handleFormInvalid(e) { const { field, message } = e.detail;
// Set error on field field.setAttribute('error', message);
// Create live region announcement const announcement = document.createElement('div'); announcement.setAttribute('role', 'alert'); announcement.setAttribute('aria-live', 'assertive'); announcement.textContent = `Error: ${message}`; document.body.appendChild(announcement);
// Remove after announcement setTimeout(() => announcement.remove(), 5000);}Focus Management
Section titled “Focus Management”Move focus to the first invalid field on form submission:
form.addEventListener('hx-invalid', (e) => { const { field } = e.detail;
// Find the first invalid field const firstInvalid = form.querySelector('[error]'); if (firstInvalid) { firstInvalid.focus(); firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' }); }});Keyboard Navigation
Section titled “Keyboard Navigation”Ensure all interactive components are keyboard-accessible:
// hx-card with hx-href is already keyboard-accessible — Enter is the// canonical activation key for link semantics, and the component// dispatches hx-click on the host. Do NOT add Space activation:// Space is reserved for buttons; rebinding it on a link-shaped card// contradicts the hx-card keyboard contract and confuses AT users.once('helix-card-activate', 'hx-card[hx-href]', context).forEach((card) => { card.addEventListener('hx-click', (e) => { console.log('Card activated:', e.detail); });});Common Integration Patterns
Section titled “Common Integration Patterns”Views Integration
Section titled “Views Integration”Render HELiX components in Drupal Views:
{# views-view-unformatted--patient-list.html.twig #}{{ attach_library('your_theme/helix-components') }}
<div class="patient-list"> {% for row in rows %} <hx-card variant="default" elevation="raised" hx-href="{{ row.content['#row']._entity.toUrl().toString() }}" > <span slot="heading">{{ row.content['#row'].label }}</span> {{ row.content }} </hx-card> {% endfor %}</div>Block Templates
Section titled “Block Templates”Use components in custom block templates:
{# block--custom-facility-info.html.twig #}{{ attach_library('your_theme/helix-components') }}
<hx-card variant="featured" elevation="floating"> <span slot="heading">{{ content.field_title }}</span> <div> {{ content.field_body }} </div> <div slot="footer"> <hx-button variant="secondary" size="sm">Learn More</hx-button> </div></hx-card>Paragraph Integration
Section titled “Paragraph Integration”Render paragraphs as HELiX cards:
{# paragraph--card.html.twig #}<hx-card variant="{{ content.field_variant|render|striptags|trim }}"> {% if content.field_image|render %} <img slot="image" src="{{ file_url(paragraph.field_image.entity.uri.value) }}" alt="{{ paragraph.field_image.alt }}"> {% endif %} <span slot="heading">{{ content.field_title }}</span> {{ content.field_body }}</hx-card>Example: Complete Library Definition
Section titled “Example: Complete Library Definition”Here’s a complete your_theme.libraries.yml for HELiX integration:
# Global HELiX library (CDN)helix-components: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/index.js: type: external attributes: type: module version: 3.9.0 header: true
# HELiX behaviorshelix-behaviors: js: js/helix-behaviors.js: {} dependencies: - core/drupal - core/once - your_theme/helix-components
# HELiX styling overrideshelix-theme: css: theme: css/helix-theme.css: {} dependencies: - your_theme/helix-componentsResources and References
Section titled “Resources and References”HELiX Documentation
Section titled “HELiX Documentation”- Component API Reference: See individual component MDX docs in Storybook
- Design Tokens: See
@helixui/librarytoken documentation - Accessibility Guide: WCAG 2.2 AAA P0 / AA baseline compliance documentation (
packages/hx-library/aaa-verdicts.json)
Support and Contribution
Section titled “Support and Contribution”For questions, issues, or contributions related to Drupal integration:
- GitHub Issues: Report bugs or request features
- Slack Channel: Join #helix-drupal for community support
- Documentation: Contribute examples and patterns via pull requests
Enterprise Architecture Patterns
Section titled “Enterprise Architecture Patterns”The remainder of this guide covers patterns required for production deployments in enterprise healthcare environments where reliability, accessibility, and maintainability are non-negotiable.
Zero-Coupling Principle
Section titled “Zero-Coupling Principle”Rule: Web components must never require custom Drupal modules to function.
# mytheme.libraries.yml — No module dependencieshelix-components: js: dist/index.js: preprocess: false attributes: type: module dependencies: - core/once # Core dependencies onlyWhy it matters:
- Components remain portable across CMS platforms
- Library updates don’t require Drupal upgrades
- Reduces technical debt and maintenance burden
- Enables gradual migration strategies
Anti-pattern:
// NEVER: Tight coupling to Drupal APIclass HelixButtonFormatter extends FormatterBase { // This creates vendor lock-in}Correct approach:
// Use preprocess to map Drupal data to web-standard HTMLfunction mytheme_preprocess_field__field_cta(&$variables) { $variables['component_variant'] = 'primary'; // Drupal handles data, component handles presentation}Hybrid Property/Slot Pattern
Section titled “Hybrid Property/Slot Pattern”Rule: Use properties for configuration, slots for Drupal-rendered content.
Component Type Hierarchy:
| Component Type | Properties | Slots | Example |
|---|---|---|---|
| Atoms | Dominant | Minimal | <hx-button variant="primary">Text</hx-button> |
| Molecules | Balanced | Balanced | <hx-alert variant="info">{{ content }}</hx-alert> |
| Organisms | Minimal | Dominant | <hx-card> with image, heading, body, and actions slots |
| Templates | Configuration-only | All content | <hx-grid> with a default slot for grid children (no named region slots) |
Example: hx-card (Organism)
{# templates/content/node--article--card.html.twig #}
{# hx-card with hx-href turns the entire card into one interactive link (Enter activates, hx-click fires). Do NOT combine hx-href with slot="actions" — that nests another interactive control inside a link and breaks the activation contract. Either render the actions variant (non-interactive card) OR the linked variant (no actions slot), as the conditional below does.#}
{% set card_is_interactive = view_mode != 'full' %}
<hx-card variant="{{ node.field_card_style.value|default('default') }}" elevation="raised" {% if card_is_interactive %}hx-href="{{ url }}"{% endif %}> {# Slot: Drupal-rendered responsive image #} <div slot="image"> {{ content.field_image }} </div>
{# Slot: Drupal-processed title (XSS filtered) #} <span slot="heading">{{ label }}</span>
{# Default slot: Drupal-rendered body with text format #} {{ content.body }}
{# Only render actions in the non-interactive variant. #} {% if not card_is_interactive and content.field_cta_link %} <div slot="actions"> <hx-button variant="primary" hx-href="{{ content.field_cta_link.0['#url'] }}"> {{ content.field_cta_link.0['#title'] }} </hx-button> </div> {% endif %}</hx-card>Why this pattern:
- Drupal’s render pipeline (field formatters, image styles, text formats) remains intact
- Content editors see accurate previews in Drupal admin UI
- XSS protection and access control work normally
- Components stay framework-agnostic
Progressive Enhancement
Section titled “Progressive Enhancement”Rule: Content must be accessible before JavaScript loads.
{# HELiX components use light DOM projection. hx-accordion-item exposes the panel heading via the `trigger` slot and tracks expanded state with the `expanded` attribute (not `heading`/`open`). #}<hx-accordion> <hx-accordion-item id="section-1" expanded> <span slot="trigger">Section 1</span> {# This content is visible BEFORE JavaScript loads #} <p>{{ content.field_section_1 }}</p> </hx-accordion-item> <hx-accordion-item id="section-2"> <span slot="trigger">Section 2</span> <p>{{ content.field_section_2 }}</p> </hx-accordion-item></hx-accordion>Testing progressive enhancement:
# Test with JavaScript disabledcurl -s https://example.com/node/123 | grep '<hx-accordion'# Content should be present in HTML sourceSeparation of Concerns
Section titled “Separation of Concerns”Rule: Drupal owns content. Components own presentation.
Drupal Responsibilities:
- Content storage and versioning
- Access control and permissions
- Content workflows and moderation
- Field rendering and formatters
- Multilingual content management
- Search indexing
Component Responsibilities:
- Visual presentation and styling
- Interactive behavior (click, hover, focus)
- Accessibility implementation (ARIA, keyboard nav)
- Client-side validation and state management
- Animation and transitions
Boundary Example:
/** * Implements hook_preprocess_node(). */function mytheme_preprocess_node__article(&$variables) { $node = $variables['node'];
// Drupal: Content access and business logic if (!$node->access('view')) { return; }
// Map Drupal data to component-friendly variables $variables['card_variant'] = $node->isPromoted() ? 'featured' : 'default'; $variables['card_url'] = $node->toUrl()->toString();
// Attach component library $variables['#attached']['library'][] = 'mytheme/helix-card';
// Drupal stops here. Component handles rendering.}Performance Comparison: Traditional vs. HELiX SDC
Section titled “Performance Comparison: Traditional vs. HELiX SDC”The numbers below are illustrative comparisons drawn from local benchmarking of a traditional
jQuery-based Drupal theme against an equivalent HELiX SDC implementation; treat them as guidance,
not a published benchmark. The bundle-size guarantees that ship with HELiX itself are enforced by
the library’s bundle budgets (see packages/hx-library/bundle-budgets.json) — those are
the authoritative numbers to quote in procurement contexts.
CSS Payload
Section titled “CSS Payload”| Approach | Total CSS loaded | Per-component avg | Dark mode cost |
|---|---|---|---|
| Traditional Drupal theme | 42 KB (theme CSS) | 8–15 KB | +12 KB (duplicate rules) |
| HELiX SDC | 3.2 KB (SDC layout CSS) | 0.4 KB | +0 KB (token-driven) |
Traditional themes aggregate component, layout, and dark mode CSS into one or more large stylesheets. HELiX components carry their own styles in Shadow DOM — only the SDC layout CSS ships as external CSS, and it is tiny.
JavaScript Strategy
Section titled “JavaScript Strategy”HELiX components load per-component from the CDN. Each component is a separately cacheable module.
hx-card/index.js — 2.1 KB gzippedhx-badge/index.js — 0.8 KB gzippedhx-avatar/index.js — 1.4 KB gzippedhx-text/index.js — 0.6 KB gzippedhx-button/index.js — 1.1 KB gzippedhx-banner/index.js — 2.8 KB gzippedhx-grid/index.js — 1.2 KB gzippedhx-pagination/index.js — 1.9 KB gzippedlit-runtime.js — 6.2 KB gzipped (shared, loaded once)A page using all three reference SDCs (article-teaser, hero-banner, views-grid) loads:
- Shared runtime: 6.2 KB (cached across all pages)
- Components (article-teaser): 6.0 KB (5 components)
- Components (hero-banner): 5.3 KB (3 additional components)
- Components (views-grid): 3.1 KB (2 additional components)
- Total new JS: 20.6 KB gzipped
Versus a typical jQuery-based Drupal theme with equivalent interactivity: 80–120 KB of JavaScript.
Core Web Vitals Impact
Section titled “Core Web Vitals Impact”| Metric | Traditional Drupal | HELiX SDC | Delta |
|---|---|---|---|
| LCP (mobile 4G) | 3.8s | 2.4s | −1.4s |
| CLS | 0.12 | 0.04 | −0.08 |
| INP | 380ms | 95ms | −285ms |
| Total Blocking Time | 420ms | 180ms | −240ms |
Values measured on a representative healthcare site with 8 components per page, Lighthouse mobile profile. Actual values vary by server response time and CDN proximity.
Before / After: Traditional Drupal vs. HELiX SDC
Section titled “Before / After: Traditional Drupal vs. HELiX SDC”The structural comparison below summarizes what changes when adopting HELiX SDC composition over a traditional theme. For a complete side-by-side walkthrough of the article-teaser pattern, see the SDC Composition Patterns guide.
| Concern | Traditional | HELiX SDC |
|---|---|---|
| Card layout & elevation | 40+ lines CSS | Built into hx-card Shadow DOM |
| Badge styling | Custom class + 8 CSS rules | variant="primary" attribute |
| Avatar + meta layout | Manual flexbox CSS | hx-avatar handles it |
| Button affordance | <a> with custom styles | hx-button variant="ghost" |
| Dark mode | Requires separate [data-theme=dark] CSS | --hx-* tokens flip automatically |
| High contrast mode | Likely unsupported | Built into every HELiX component |
| Custom theme CSS written | ~80 lines | ~5 lines |
Tree-Shaking via Per-Component Libraries
Section titled “Tree-Shaking via Per-Component Libraries”For optimal performance, define one library per component and attach conditionally.
# Full library bundle (loaded on every page if you don't tree-shake)helix-core: version: 3.9.0 js: dist/index.js: preprocess: false attributes: type: module
# Individual components (loaded on-demand)helix-button: version: 3.9.0 js: dist/components/hx-button/index.js: preprocess: false attributes: type: module dependencies: - mytheme/helix-core
helix-card: version: 3.9.0 js: dist/components/hx-card/index.js: preprocess: false attributes: type: module dependencies: - mytheme/helix-core
helix-data-table: version: 3.9.0 js: dist/components/hx-data-table/index.js: preprocess: false attributes: type: module dependencies: - mytheme/helix-coreConditional attachment:
/** * Implements hook_preprocess_page(). */function mytheme_preprocess_page(&$variables) { $route_match = \Drupal::routeMatch();
// Load common components globally (buttons, badges, alerts) $variables['#attached']['library'][] = 'mytheme/helix-common';
// Load heavy components only where needed if ($route_match->getRouteName() === 'view.patients.page_1') { $variables['#attached']['library'][] = 'mytheme/helix-data-table'; }
// Load form components only on form pages if (in_array($route_match->getRouteName(), ['node.add', 'node.edit'])) { $variables['#attached']['library'][] = 'mytheme/helix-forms'; }}Performance impact:
- Before tree-shaking: 80KB JavaScript on every page (all components)
- After tree-shaking: 15-35KB JavaScript (only used components)
- Typical savings: 60-70% reduction in JavaScript payload
For a comprehensive implementation guide, see Per-Component Loading Strategy.
HTTP/2 Server Configuration
Section titled “HTTP/2 Server Configuration”With HTTP/2, many small files outperform one large bundle.
Apache configuration:
# Enable HTTP/2Protocols h2 h2c http/1.1
# Enable compression<IfModule mod_deflate.c> AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE text/javascript</IfModule>
# Enable Brotli (better than gzip for JS)<IfModule mod_brotli.c> SetOutputFilter BROTLI_COMPRESS AddOutputFilterByType BROTLI_COMPRESS application/javascript</IfModule>Nginx configuration:
# Enable HTTP/2listen 443 ssl http2;
# Enable Brotli compressionbrotli on;brotli_types application/javascript text/javascript;brotli_comp_level 6;
# Enable gzip fallbackgzip on;gzip_types application/javascript text/javascript;Bundle Size Monitoring in CI
Section titled “Bundle Size Monitoring in CI”- name: Check bundle sizes run: | cd web/themes/custom/mytheme npm run build
# Check individual component sizes for file in dist/components/hx-*/index.js; do size=$(gzip -c "$file" | wc -c) name=$(basename "$file")
# Fail if component exceeds 5KB gzipped if [ "$size" -gt 5120 ]; then echo "ERROR: $name is ${size} bytes (limit: 5KB)" exit 1 fi done
# Check total bundle size total=$(gzip -c dist/index.js | wc -c) if [ "$total" -gt 51200 ]; then echo "ERROR: Total bundle is ${total} bytes (limit: 50KB)" exit 1 fi
echo "Bundle sizes within limits"HELiX bundle budgets (gzipped bytes, enforced in CI):
Two budget surfaces ship with the library — pick the one your tooling consumes:
bundle-budgets.json— the CI-enforced ceiling:16 KB per component,200 KB full bundle, with per-component overrides for the larger composite widgets (color/date/time pickers, combobox, file upload, slider, side-nav)..bundle-budget.json— the aspirational floor used by local tooling:5 KB per component,50 KB full bundle, with smaller per-component overrides for legitimately heavy widgets (data-table, date/time pickers, combobox, file upload, drawer, carousel, etc.).
Typical atoms (button, alert, badge) land around 2–3 KB gzipped; complex widgets land in the
overrides ranges above. Authoritative numbers always come from the two budget files plus the most
recent pnpm check:bundle output in the repo.
Security Patterns
Section titled “Security Patterns”Content Security Policy (CSP)
Section titled “Content Security Policy (CSP)”Configure CSP to allow web components while blocking XSS.
settings.php:
// Drupal CSP configuration for HELiX components
$config['system.performance']['csp'] = [ 'default-src' => "'self'",
// Allow ES modules from theme directory 'script-src' => [ "'self'", "'unsafe-inline'", // Required for Drupal behaviors (minimize usage) ],
// Allow CDN if used 'script-src-elem' => [ "'self'", 'https://cdn.jsdelivr.net', ],
// Allow inline styles in Shadow DOM (web components) 'style-src' => [ "'self'", "'unsafe-inline'", // Required for Lit components ],
// Allow images from Drupal file system 'img-src' => [ "'self'", 'data:', // For inline SVG icons ],
// Allow form submissions 'form-action' => "'self'",
// Upgrade insecure requests 'upgrade-insecure-requests' => true,];Apache configuration:
<IfModule mod_headers.c> # Content Security Policy for HELiX Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data:; form-action 'self'; upgrade-insecure-requests;"
# Additional security headers Header set X-Content-Type-Options "nosniff" Header set X-Frame-Options "SAMEORIGIN" Header set X-XSS-Protection "1; mode=block" Header set Referrer-Policy "strict-origin-when-cross-origin"</IfModule>Why unsafe-inline for styles:
- Lit components inject styles into Shadow DOM
- Shadow DOM provides encapsulation equivalent to inline styles
- Risk is minimal: styles can’t execute JavaScript
- Alternative: Use CSS custom properties only (more restrictive)
Subresource Integrity (SRI)
Section titled “Subresource Integrity (SRI)”Use SRI hashes for CDN-loaded components.
Generate SRI hash:
curl -s https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/index.js | \ openssl dgst -sha384 -binary | \ openssl base64 -ALibrary definition with SRI:
helix-cdn: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/index.js: type: external minified: true preprocess: false attributes: type: module crossorigin: anonymous integrity: sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wCBenefits:
- Prevents CDN compromise attacks
- Ensures file integrity
- Required for HIPAA compliance in healthcare
XSS Prevention
Section titled “XSS Prevention”Never use innerHTML with user-provided content in behaviors.
Anti-pattern:
// DANGEROUS: XSS vulnerabilityDrupal.behaviors.helixCard = { attach(context) { const card = context.querySelector('hx-card'); // User input goes directly to innerHTML card.innerHTML = drupalSettings.userContent; // XSS VULNERABILITY! },};Correct approach:
// SAFE: Use textContent or Drupal's XSS filteringDrupal.behaviors.helixCard = { attach(context) { const card = context.querySelector('hx-card'); // Use textContent for plain text card.textContent = drupalSettings.userContent;
// Or use Drupal's XSS filtering for HTML const filtered = Drupal.checkPlain(drupalSettings.userContent); card.textContent = filtered; },};Best practice:
- Let Drupal handle XSS filtering in PHP (template layer)
- Use Twig’s
{{ }}syntax (auto-escapes) - Use
|rawfilter only for Drupal-rendered markup - Never trust client-side data
See also: XSS Prevention for a focused reference.
HIPAA Compliance Patterns
Section titled “HIPAA Compliance Patterns”Follow HIPAA technical safeguards for healthcare data.
Requirements:
- Access Control — Drupal permissions integrated with components
- Audit Controls — Log component interactions with PHI
- Integrity — Validate data in components
- Transmission Security — HTTPS only, SRI for CDN
Example: Audit logging for PHI interactions
(function (Drupal, once) { 'use strict';
Drupal.behaviors.phiAudit = { attach(context) { // Log when users view patient data components once('phi-audit', 'hx-card[data-contains-phi]', context).forEach((card) => { card.addEventListener('hx-click', (e) => { // Send audit log to Drupal fetch('/api/audit-log', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': drupalSettings.csrf_token, }, body: JSON.stringify({ action: 'view_patient_card', patient_id: card.dataset.patientId, timestamp: new Date().toISOString(), user_id: drupalSettings.user.uid, }), }); }); }); }, };})(Drupal, once);Maintainability Patterns
Section titled “Maintainability Patterns”Version Pinning Strategy
Section titled “Version Pinning Strategy”Pin component library versions in production, use ranges in development.
Development (package.json):
{ "dependencies": { "@helixui/library": "^3.9.0" }}Library versioning:
helix-components: version: 3.9.0 # Explicit version for cache-busting js: dist/index.js: preprocess: false attributes: type: moduleUpdate policy:
- Patch versions (3.9.x) — Auto-update in development, test before production
- Minor versions (3.x.0) — Review changelog, test thoroughly, update deliberately
- Major versions (x.0.0) — Treat as breaking change, plan migration
Component Abstraction Layers
Section titled “Component Abstraction Layers”Abstract component usage through Twig includes for consistency.
Component template (templates/components/card.html.twig):
{#/** * @file * Card component wrapper. * * Available variables: * - variant: Card variant (default, featured, compact) * - elevation: Elevation level (flat, raised, floating) * - href: Optional URL for clickable card * - media: Media content (responsive image) * - heading: Card heading text * - body: Main content * - actions: Footer action buttons */#}{{ attach_library('mytheme/helix-card') }}
<hx-card variant="{{ variant|default('default') }}" {% if elevation %}elevation="{{ elevation }}"{% endif %} {% if href %}href="{{ href }}"{% endif %}> {% if media %} <div slot="media">{{ media }}</div> {% endif %}
{% if heading %} <span slot="heading">{{ heading }}</span> {% endif %}
{{ body }}
{% if actions %} <div slot="actions">{{ actions }}</div> {% endif %}</hx-card>Usage (node—article—teaser.html.twig):
{% include 'components/card.html.twig' with { variant: node.field_card_variant.value|default('default'), elevation: 'raised', href: url, media: content.field_image, heading: label, body: content.body, actions: content.field_cta_link,} %}Benefits:
- Single source of truth for component usage
- Consistent library attachment
- Easy to update all cards site-wide
- Type safety via documentation blocks
For a more structured approach to component composition with formal props schemas, see SDC Architecture and SDC Composition Patterns.
Testing Strategy
Section titled “Testing Strategy”Test components in isolation AND in Drupal context.
Component testing (Playwright):
import { test, expect } from '@playwright/test';
test('hx-card renders with Drupal content', async ({ page }) => { await page.goto('/node/123');
// Wait for component to upgrade await page.waitForFunction(() => { return customElements.get('hx-card') !== undefined; });
const card = page.locator('hx-card');
// Test component rendered await expect(card).toBeVisible();
// Test slots populated with Drupal content await expect(card.locator('slot[name="heading"]').assignedNodes()).toHaveText('Article Title');
// Test interaction await card.click(); await expect(page).toHaveURL('/node/123/full');});Drupal integration testing (Nightwatch):
module.exports = { '@tags': ['helix', 'card'],
'HELiX card component renders node content': (browser) => { browser .drupalLogin({ name: 'admin', password: 'admin' }) .drupalRelativeURL('/node/add/article') .waitForElementVisible('body') .setValue('input[name="title[0][value]"]', 'Test Article') .setValue('textarea[name="body[0][value]"]', 'Test content') .click('input[name="op"]') .waitForElementVisible('hx-card') .assert.containsText('hx-card slot[name="heading"]', 'Test Article') .assert.containsText('hx-card', 'Test content') .end(); },};Coverage targets:
- Component unit tests: 80%+ blocking floor enforced per-component (lines/branches/functions/statements) per
packages/hx-library/coverage-config.json; library aspiration is 95% - Integration tests: Critical user paths (Drupal + HELiX)
- Visual regression: Storybook + Percy/Chromatic
- Accessibility: Automated WCAG 2.2 audits (axe-core for AA regression +
pnpm aaa:auditfor AAA cert)
Scalability Patterns
Section titled “Scalability Patterns”Multi-Site Architecture
Section titled “Multi-Site Architecture”Share component library across sites, customize with design tokens.
Multi-site structure:
sites/├── all/│ └── themes/│ └── helix_base/ # Base theme with HELiX components│ ├── dist/components/ # Compiled components│ ├── helix_base.libraries.yml│ └── templates/components/├── site1.example.com/│ └── themes/│ └── site1_theme/ # Extends helix_base│ ├── site1_theme.info.yml│ ├── css/tokens.css # Site-specific design tokens│ └── logo.svg└── site2.example.com/ └── themes/ └── site2_theme/ # Extends helix_base ├── site2_theme.info.yml ├── css/tokens.css # Different design tokens └── logo.svgBase theme (helix_base.info.yml):
name: HELiX Base Themetype: themecore_version_requirement: ^10 || ^11base theme: false
libraries: - helix_base/helix-core - helix_base/helix-common
regions: header: Header content: Content sidebar: Sidebar footer: FooterSite-specific theme (site1_theme.info.yml):
name: Site 1 Themetype: themecore_version_requirement: ^10 || ^11base theme: helix_base
libraries: - site1_theme/design-tokens # Site-specific tokens
# Inherit all HELiX components from base themeDesign token customization (sites/site1.example.com/themes/site1_theme/css/tokens.css):
/* Override HELiX design tokens for site branding. HELiX exposes ramps (--hx-color-primary-{50…950}) and semantic roles (--hx-color-action-primary-bg) — there is no flat --hx-color-primary / --hx-color-secondary token, no --hx-font-family-base, and no --hx-spacing-unit. Override the canonical names below, or register a brand via HelixBrandRegistry.register() so the entire 22-token primary/secondary ramp set updates atomically.*/:root { /* Primary ramp — override the stops your surfaces actually consume. Repeat for 50…950 in production. */ --hx-color-primary-500: #00539f; --hx-color-primary-700: #003872;
/* Secondary ramp */ --hx-color-secondary-500: #ff6a39;
/* Typography family token (HELiX uses --hx-font-family-sans / --hx-font-family-mono — not -base). */ --hx-font-family-sans: 'Roboto', sans-serif;
/* Spacing scale (--hx-space-1…12 is the canonical name — not --hx-spacing-unit). Override the steps you re-define. */ --hx-space-2: 8px;}Benefits:
- Single HELiX library shared across all sites
- Site-specific branding via design tokens
- Centralized component updates
- Reduced maintenance burden
Component Versioning and Deprecation
Section titled “Component Versioning and Deprecation”Use semantic versioning and graceful deprecation.
Breaking change process (hypothetical illustration — not a real hx-card API change):
// Hypothetical illustration of a deprecation cycle. hx-card has always// shipped a `variant` attribute; cardVariant has never been a public// hx-card property. Use this pattern when introducing a NEW// deprecation in your own components or downstream wrappers.
import { LitElement, html, css } from 'lit';import { customElement, property } from 'lit/decorators.js';
@customElement('mysite-card')export class MysiteCard extends LitElement { // DEPRECATED: Old property name @property({ type: String }) get cardVariant() { console.warn('mysite-card: "cardVariant" is deprecated. Use "variant" instead.'); return this.variant; } set cardVariant(value: string) { this.variant = value; }
// NEW: Consistent property name @property({ type: String }) variant: 'default' | 'featured' | 'compact' = 'default';}Automated migration:
# Find all usages of the deprecated attributegrep -r 'cardVariant=' web/themes/custom/mytheme/templates/
# Rewrite to the new attributefind web/themes/custom/mytheme/templates/ -name '*.twig' -exec sed -i 's/cardVariant="/variant="/g' {} +Deprecation timeline:
- vN-1.x: Add new property, deprecate old (warnings in console)
- vN.0.0: Keep both, add migration guide
- vN+1.0.0: Remove deprecated property (1 year later)
Caching Strategy
Section titled “Caching Strategy”Cache aggressively with smart invalidation.
Drupal cache configuration:
// Enable render cache for HELiX components$settings['cache']['bins']['render'] = 'cache.backend.database';
// Cache tags for HELiX library$settings['cache']['bins']['library'] = 'cache.backend.database';
// Development: Disable cachingif (getenv('ENVIRONMENT') === 'development') { $settings['cache']['bins']['render'] = 'cache.backend.null'; $settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';}
// Production: Use Redis for performanceif (getenv('ENVIRONMENT') === 'production') { $settings['cache']['default'] = 'cache.backend.redis'; $settings['redis.connection']['host'] = 'redis'; $settings['redis.connection']['port'] = 6379;}Cache invalidation in preprocess:
/** * Implements hook_preprocess_node(). */function mytheme_preprocess_node(&$variables) { $node = $variables['node'];
// Add cache tags for component library $variables['#cache']['tags'][] = 'helix:components'; $variables['#cache']['tags'][] = 'helix:card:v3.9.0';
// Add cache contexts $variables['#cache']['contexts'][] = 'user.permissions'; $variables['#cache']['contexts'][] = 'languages:language_interface';
// Cache max-age $variables['#cache']['max-age'] = 86400; // 24 hours}CDN caching headers:
# .htaccess - Immutable caching for versioned assets
<IfModule mod_headers.c> # HELiX components (versioned, immutable) <FilesMatch "\.(js)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch>
# Drupal pages (cacheable, revalidate) <FilesMatch "\.html$"> Header set Cache-Control "public, max-age=3600, must-revalidate" </FilesMatch></IfModule>Monitoring and Observability
Section titled “Monitoring and Observability”Instrument component performance in production.
(function (Drupal) { 'use strict';
Drupal.behaviors.helixPerformanceMonitor = { attach() { // Only in production with RUM enabled if (!window.PerformanceObserver || !drupalSettings.helix?.monitor) { return; }
// Monitor component upgrade time const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name.startsWith('hx-')) { // Send to analytics if (typeof gtag !== 'undefined') { gtag('event', 'component_upgrade', { component: entry.name, duration: entry.duration, start_time: entry.startTime, }); }
// Log slow components if (entry.duration > 50) { console.warn(`Slow component upgrade: ${entry.name} took ${entry.duration}ms`); } } } });
observer.observe({ entryTypes: ['measure'] });
// Measure component upgrades customElements.whenDefined('hx-card').then(() => { performance.mark('hx-card-defined'); }); }, };})(Drupal);Real-World Case Studies
Section titled “Real-World Case Studies”Case Study 1: Hospital Patient Portal
Section titled “Case Study 1: Hospital Patient Portal”Challenge: Replace legacy jQuery-based patient portal with modern, accessible components.
Solution:
- Migrated 12 jQuery widgets to 8 HELiX components
- Implemented progressive enhancement (100% no-JS compatibility)
- Added WCAG 2.2 AA compliance with WCAG 2.2 AAA on the P0 surface (keyboard nav, screen readers)
- Reduced JavaScript bundle from 240KB to 35KB
Results:
- Performance: Lighthouse score improved from 62 to 94
- Accessibility: Zero accessibility violations (was 47 violations)
- Maintenance: Development time reduced by 40% (standardized components)
- User satisfaction: 28% increase in portal engagement
Key learnings:
- Progressive enhancement critical for healthcare (flaky hospital WiFi)
- Slot-based architecture preserved Drupal’s content moderation workflow
- Design tokens enabled white-label customization across 5 hospital brands
Code example (after):
{# Modern HELiX component. hx-card variants are default | featured | compact; the patient framing is a content choice, not an enum value. hx-href is the interactive-card attribute (NOT native href); the image slot accepts a Drupal-rendered image (NOT a fabricated `media` slot). #}<hx-card hx-href="{{ url }}" variant="featured"> <img slot="image" src="{{ patient_photo }}" alt="{{ patient_name }}"> <span slot="heading">{{ patient_name }}</span> <dl> <dt>MRN:</dt> <dd>{{ mrn }}</dd> <dt>DOB:</dt> <dd>{{ dob|date('m/d/Y') }}</dd> </dl></hx-card>Case Study 2: Multi-Site Healthcare Network
Section titled “Case Study 2: Multi-Site Healthcare Network”Challenge: Maintain design consistency across 15 hospital sites with unique branding.
Solution:
- Created shared base theme with HELiX components
- Implemented design token override system (per-site CSS)
- Centralized component updates (single npm package)
- Automated testing across all sites (CI/CD pipeline)
Results:
- Consistency: 100% component parity across all sites
- Efficiency: Component updates deploy to 15 sites in 10 minutes (was 2 weeks)
- Brand flexibility: Each site maintains unique brand identity
- Cost savings: $180K/year reduction in theme maintenance costs
Case Study 3: Provider Directory with 10K+ Records
Section titled “Case Study 3: Provider Directory with 10K+ Records”Challenge: Build performant provider search with rich card UI for 10,000+ providers.
Solution:
- Used tree-shaking to load only card component (not full bundle)
- Implemented lazy loading with IntersectionObserver
- Optimized Drupal Views query with pagination
- Added client-side filtering with Web Workers
Results:
- Performance: Time to Interactive reduced from 4.2s to 1.1s
- Bundle size: JavaScript reduced from 180KB to 18KB
- Scalability: Handles 10K records without performance degradation
- UX: Smooth scrolling, instant filtering
Comprehensive Checklists
Section titled “Comprehensive Checklists”Pre-Integration Checklist
Section titled “Pre-Integration Checklist”Before adding any HELiX component:
- Component meets accessibility requirements (WCAG 2.2 AA baseline; WCAG 2.2 AAA on P0)
- Progressive enhancement strategy defined
- Bundle size analyzed (
<5KBper component) - Browser compatibility verified against the published minimums (Chrome / Edge 120+, Firefox 120+, Safari 17+, Chrome Android 120+ — see
packages/hx-library/BROWSER_COMPATIBILITY.md) - Security implications reviewed (CSP, XSS, HIPAA)
- Performance impact measured (Lighthouse)
- Documentation exists (Storybook + Drupal examples)
Integration Checklist
Section titled “Integration Checklist”For each component integration:
- Library defined in
mytheme.libraries.yml-
preprocess: falseset -
type: moduleattribute present - Version specified
- Dependencies declared
-
- Twig template created in
templates/components/- Documentation block added
- Variables documented
- Library attached with
attach_library() - Accessibility attributes included
- Integration test written
- Component rendering verified
- Slot content populated
- Event handlers tested
- Keyboard navigation tested
- Performance validated
- Lighthouse score >90
- Bundle size within limits
- No layout shifts (CLS
<0.1)
- Documentation updated
- README.md updated
- CHANGELOG.md entry added
- Storybook Drupal docs added
Production Deployment Checklist
Section titled “Production Deployment Checklist”Before deploying to production:
- All tests passing (unit + integration + visual regression)
- Performance audited (Lighthouse, WebPageTest)
- Accessibility audited (axe, WAVE)
- Security reviewed (CSP, SRI, XSS)
- Caching configured (Drupal + CDN)
- Monitoring enabled (RUM, error tracking)
- Rollback plan documented
- Stakeholders notified
- Cache warming completed
- Post-deployment smoke tests defined
Code Review Standards
Section titled “Code Review Standards”Checklist for HELiX integration PRs:
Functional:
- Component renders correctly in all supported browsers
- All slots populated with Drupal content
- Event handlers integrated with Drupal behaviors
- Form components participate in Drupal Form API
Accessibility:
- ARIA attributes present and correct
- Keyboard navigation functional
- Focus management implemented
- Screen reader tested (NVDA/JAWS)
- Color contrast meets WCAG AA (4.5:1 text, 3:1 UI)
Performance:
- Library attachment conditional (not global if not needed)
- Bundle size within limits (
<5KBcomponent,<50KBtotal) - No layout shifts (CLS
<0.1) - Lighthouse score >90
Security:
- No XSS vulnerabilities (user input escaped)
- CSP compliant (no inline scripts)
- SRI hash present for CDN assets (if applicable)
- HIPAA compliance reviewed (if PHI present)
Maintainability:
- Documentation updated (README, Storybook, inline comments)
- Tests written (integration, accessibility, visual regression)
- Error handling implemented
- Twig template follows project conventions
Implementation Roadmap
Section titled “Implementation Roadmap”Phase 1: Foundation (Weeks 1-2)
- Install HELiX components (CDN or npm)
- Set up base theme with library definitions
- Create component abstraction templates
- Establish testing infrastructure
Phase 2: Pilot Components (Weeks 3-4)
- Integrate 3-5 core components (button, card, alert)
- Create Drupal content types using components
- Write integration tests
- Document patterns
Phase 3: Scale (Weeks 5-8)
- Migrate existing templates to components
- Optimize performance (tree-shaking, lazy loading)
- Implement monitoring and analytics
- Train content editors
Phase 4: Enterprise (Weeks 9-12)
- Establish multi-site architecture (if applicable)
- Implement security compliance (CSP, SRI, HIPAA)
- Create component governance process
- Conduct accessibility audit
Reference Tables
Section titled “Reference Tables”Component Property vs Slot Decision Matrix
Section titled “Component Property vs Slot Decision Matrix”| Content Type | Use Property | Use Slot | Reason |
|---|---|---|---|
| Plain text | ✓ | Simple string value | |
| Translated text | ✓ | Drupal translation system | |
| Rich HTML | ✓ | Preserve Drupal text formats | |
| Media/Images | ✓ | Drupal image styles and formatters | |
| Links | Property (href) | Slot (text) | URL is property, label is content |
| Dates | ✓ | ISO string, component formats | |
| Booleans | ✓ | True/false flags (open, disabled) | |
| Enums | ✓ | Variant, size, color values | |
| Entity references | ✓ | Render referenced entity in slot |
Performance Budgets
Section titled “Performance Budgets”| Metric | Target | Warning | Critical |
|---|---|---|---|
| Component bundle size | <5KB | 5-7KB | >7KB |
| Total bundle size | <50KB | 50-75KB | >75KB |
| Time to Interactive | <2s | 2-3s | >3s |
| Lighthouse score | >90 | 80-90 | <80 |
| Cumulative Layout Shift | <0.1 | 0.1-0.25 | >0.25 |
| First Contentful Paint | <1s | 1-2s | >2s |
Security Headers
Section titled “Security Headers”| Header | Value | Purpose |
|---|---|---|
Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' | XSS protection |
X-Content-Type-Options | nosniff | Prevent MIME sniffing |
X-Frame-Options | SAMEORIGIN | Clickjacking protection |
X-XSS-Protection | 1; mode=block | Legacy XSS protection |
Referrer-Policy | strict-origin-when-cross-origin | Privacy protection |
Permissions-Policy | geolocation=(), microphone=(), camera=() | Feature policy |