Documentation Index
Fetch the complete documentation index at: https://mintlify.com/finsweet/attributes/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Attributes by Finsweet is a modular JavaScript library that dynamically loads and executes individual attribute solutions on demand. The core library handles initialization, dependency management, and provides a unified API through the global window.FinsweetAttributes object.
Architecture
The system is built on three core principles:
- Dynamic Loading - Attribute packages are only loaded when needed
- Monorepo Structure - Each attribute is a standalone package
- Global API - Unified interface for all attributes through
window.FinsweetAttributes
Initialization Flow
1. Script Execution
When the core library script loads, it immediately runs the init() function:
// From packages/attributes/src/attributes.ts:19
const init = () => {
const { FinsweetAttributes } = window;
// Avoid initting the Attributes API more than once
if (FinsweetAttributes && !Array.isArray(FinsweetAttributes)) {
initAttributes();
return;
}
// Collect pre-existing callbacks
const callbacks = Array.isArray(FinsweetAttributes)
? (FinsweetAttributes as FinsweetAttributesCallback[])
: [];
// Init Attributes object
window.FinsweetAttributes = {
version,
scripts: [],
modules: {},
process: new Set<FinsweetAttributeKey>(),
utils: { fetchPage, attachExternalStylesheets },
load: initAttribute,
push(...args) { /* ... */ },
destroy() { /* ... */ }
};
};
The initialization is designed to run safely multiple times. If window.FinsweetAttributes already exists as an object (not an array), it skips re-initialization and only initializes the individual attributes.
2. Detection Methods
The library supports two detection methods:
Method A: Script Tag Declaration
Declare which attributes to load directly on the <script> tag:
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@latest/attributes.js"
fs-accordion
fs-modal
></script>
The library scans for script tags with fs-{attribute} attributes:
// From packages/attributes/src/attributes.ts:82-87
for (const key of ATTRIBUTE_KEYS) {
const isDefined = script.hasAttribute(`fs-${key}`);
if (!isDefined) continue;
initAttribute(key);
}
Method B: Auto-Detection
Enable automatic detection by adding fs-attributes-auto="true":
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@latest/attributes.js"
fs-attributes-auto="true"
></script>
With auto-detection enabled, the library scans the entire DOM:
// From packages/attributes/src/attributes.ts:98-114
const usedAttributes = new Set<FinsweetAttributeKey>();
const allElements = document.querySelectorAll('*');
for (const element of allElements) {
for (const name of element.getAttributeNames()) {
const fsMatch = name.match(/^fs-([^-]+)/);
const key = fsMatch?.[1] as FinsweetAttributeKey | undefined;
if (key && ATTRIBUTE_KEYS.has(key)) {
usedAttributes.add(key);
}
}
}
for (const attribute of usedAttributes) {
initAttribute(attribute);
}
Auto-detection is convenient but scans every element in the DOM. For performance-critical applications with large DOMs, explicit script tag declaration is recommended.
3. Package Loading
Each attribute is loaded dynamically using dynamic imports:
// From packages/attributes/src/load.ts:7-11
export const loadAttribute = async (key: FinsweetAttributeKey) => {
switch (key) {
case 'accordion': {
return import('@finsweet/attributes-accordion');
}
// ... other attributes
}
};
This approach enables:
- Code splitting - Only load what’s needed
- Lazy loading - Defer loading until runtime
- Bundle optimization - Reduce initial payload size
4. Attribute Initialization
Once loaded, each attribute package is initialized:
// From packages/attributes/src/attributes.ts:124-166
const initAttribute = async (key: FinsweetAttributeKey) => {
// Ensure the attribute is only initted once
if (window.FinsweetAttributes.process.has(key)) return;
window.FinsweetAttributes.process.add(key);
// Init controls
const controls = (window.FinsweetAttributes.modules[key] ||= {});
controls.loading = new Promise((resolve) => {
controls.resolve = (value) => {
resolve(value);
delete controls.resolve;
};
});
// Load Attribute package
try {
const { init, version } = await loadAttribute(key);
// Init attribute
const { result, destroy } = (await init()) || {};
// Finalize controls
controls.version = version;
controls.destroy = () => {
destroy?.();
window.FinsweetAttributes.process.delete(key);
};
controls.restart = () => {
controls.destroy?.();
return window.FinsweetAttributes.load(key);
};
controls.resolve?.(result);
return result;
} catch (err) {
console.error(err);
}
};
Runtime Behavior
Deduplication
The library prevents duplicate initialization:
- Script-level: Tracks processed script tags in
window.FinsweetAttributes.scripts
- Attribute-level: Uses a
Set in window.FinsweetAttributes.process to track active attributes
// From packages/attributes/src/attributes.ts:76-77
if (window.FinsweetAttributes.scripts.includes(script)) return;
window.FinsweetAttributes.scripts.push(script);
Lifecycle Management
Each initialized attribute gets lifecycle controls:
const controls = window.FinsweetAttributes.modules.accordion;
// Wait for initialization
await controls.loading;
// Restart the attribute
await controls.restart?.();
// Destroy the attribute
controls.destroy?.();
Callback System
Run code after an attribute loads using the push method:
window.FinsweetAttributes = window.FinsweetAttributes || [];
window.FinsweetAttributes.push(
['accordion', (result) => {
console.log('Accordion loaded:', result);
}],
['modal', (result) => {
console.log('Modal loaded:', result);
}]
);
Callbacks can be pushed before the library loads. The core library collects any pre-existing callbacks from the array and executes them after initialization.
DOM Ready Handling
The library uses a two-phase initialization to handle different script loading scenarios:
// From packages/attributes/src/attributes.ts:90-94
getScripts().forEach(initScript);
await waitDOMReady();
getScripts().forEach(initScript);
- Immediate: Process scripts that exist when the core library executes
- After DOM Ready: Process scripts added dynamically or after DOM ready
This ensures attributes work regardless of when the script tag is loaded.
Error Handling
The library includes error handling at the attribute level:
try {
const { init, version } = await loadAttribute(key);
const { result, destroy } = (await init()) || {};
// ... initialization
} catch (err) {
console.error(err);
}
Errors during attribute loading or initialization are:
- Logged to the console
- Isolated to the failing attribute (other attributes continue to work)
- Do not crash the entire library
Code Splitting Benefits
- Smaller initial bundle: Core library is ~2KB gzipped
- On-demand loading: Attributes load only when used
- Parallel loading: Multiple attributes can load simultaneously
Optimization Recommendations
- Use explicit declaration instead of auto-detection for large DOMs
- Load only needed attributes via script tag attributes
- Leverage browser caching with CDN delivery
- Defer non-critical attributes using callbacks
The modular architecture means you only pay the performance cost for attributes you actually use. An empty page with just the core library adds minimal overhead.