${doc.summary}
") + append("Example: ${doc.example}
") + append("${doc.details}
") + append("x-data=\"{ open: false }\"",
+ "Everything inside a tag with x-data becomes an Alpine component. " +
+ "Any data properties declared will be reactive — when they change, the DOM updates automatically."
+ ),
+ "x-init" to DirectiveDoc(
+ "x-init",
+ "Run an expression when a component is initialized.",
+ "x-init=\"date = new Date()\"",
+ "Runs the given expression once when the component is initialized. " +
+ "Can also be used to run async code using await."
+ ),
+ "x-show" to DirectiveDoc(
+ "x-show",
+ "Toggle the visibility of an element.",
+ "x-show=\"open\"",
+ "Sets display: none on the element when the expression evaluates to false. " +
+ "Works with x-transition for animated show/hide."
+ ),
+ "x-bind" to DirectiveDoc(
+ "x-bind",
+ "Dynamically set HTML attributes on an element.",
+ "x-bind:class=\"{ active: isActive }\" or :class=\"{ active: isActive }\"",
+ "Sets the value of an attribute to the result of a JavaScript expression. " +
+ "Shorthand: :attribute."
+ ),
+ "x-on" to DirectiveDoc(
+ "x-on",
+ "Listen for browser events on an element.",
+ "x-on:click=\"open = !open\" or @click=\"open = !open\"",
+ "Attaches an event listener to the element. Shorthand: @event. " +
+ "Supports modifiers like .prevent, .stop, .window, etc."
+ ),
+ "x-text" to DirectiveDoc(
+ "x-text",
+ "Set the text content of an element.",
+ "x-text=\"message\"",
+ "Sets the inner text of the element to the result of the expression. " +
+ "HTML is escaped automatically."
+ ),
+ "x-html" to DirectiveDoc(
+ "x-html",
+ "Set the inner HTML of an element.",
+ "x-html=\"content\"",
+ "Sets the inner HTML of the element. Warning: only use with trusted content " +
+ "to avoid XSS vulnerabilities."
+ ),
+ "x-model" to DirectiveDoc(
+ "x-model",
+ "Two-way bind a form input to Alpine data.",
+ "x-model=\"search\"",
+ "Keeps the value of an input element in sync with a data property. " +
+ "Supports modifiers: .lazy, .number, .debounce, .throttle."
+ ),
+ "x-modelable" to DirectiveDoc(
+ "x-modelable",
+ "Expose a property for external binding via x-model.",
+ "x-modelable=\"value\"",
+ "Allows a component property to be bound from outside using x-model."
+ ),
+ "x-for" to DirectiveDoc(
+ "x-for",
+ "Repeat a block of HTML based on a data set.",
+ "<template x-for=\"item in items\">",
+ "Must be used on a <template> tag. Iterates over arrays or ranges."
+ ),
+ "x-transition" to DirectiveDoc(
+ "x-transition",
+ "Apply CSS transitions to show/hide elements.",
+ "x-transition or x-transition:enter=\"transition ease-out\"",
+ "Adds transition classes during enter/leave phases. Can specify classes for each phase: " +
+ ":enter, :enter-start, :enter-end, " +
+ ":leave, :leave-start, :leave-end."
+ ),
+ "x-effect" to DirectiveDoc(
+ "x-effect",
+ "Run a reactive side effect.",
+ "x-effect=\"console.log(count)\"",
+ "Re-runs the expression whenever any reactive data it references changes."
+ ),
+ "x-ref" to DirectiveDoc(
+ "x-ref",
+ "Reference an element directly via \$refs.",
+ "x-ref=\"input\" then \$refs.input.focus()",
+ "Creates a named reference to the DOM element accessible via \$refs."
+ ),
+ "x-if" to DirectiveDoc(
+ "x-if",
+ "Conditionally add/remove a block of HTML.",
+ "<template x-if=\"open\">",
+ "Must be used on a <template> tag. Unlike x-show, " +
+ "this completely adds/removes the element from the DOM."
+ ),
+ "x-id" to DirectiveDoc(
+ "x-id",
+ "Scope generated IDs with \$id().",
+ "x-id=\"['text-input']\"",
+ "Provides a scoping context for IDs generated via the \$id() magic."
+ ),
+ "x-cloak" to DirectiveDoc(
+ "x-cloak",
+ "Hide the element until Alpine initializes.",
+ "x-cloak",
+ "Hides the element until Alpine finishes initializing, preventing flash of unstyled content. " +
+ "Requires a CSS rule: [x-cloak] { display: none !important; }."
+ ),
+ "x-ignore" to DirectiveDoc(
+ "x-ignore",
+ "Prevent Alpine from initializing a section of HTML.",
+ "x-ignore",
+ "Tells Alpine to skip this element and all its children."
+ ),
+ "x-teleport" to DirectiveDoc(
+ "x-teleport",
+ "Move an element to another location in the DOM.",
+ "<template x-teleport=\"body\">",
+ "Must be used on a <template> tag. Moves the template content " +
+ "to the target selector."
+ ),
+ "x-mask" to DirectiveDoc(
+ "x-mask",
+ "Apply an input mask to an element.",
+ "x-mask=\"(999) 999-9999\"",
+ "Constrains user input to match the specified pattern. 9 for digits, " +
+ "a for letters, * for both."
+ ),
+ "x-intersect" to DirectiveDoc(
+ "x-intersect",
+ "Trigger when element enters or leaves the viewport.",
+ "x-intersect=\"shown = true\"",
+ "Uses the Intersection Observer API to detect when an element is visible."
+ ),
+ "x-trap" to DirectiveDoc(
+ "x-trap",
+ "Trap keyboard focus within an element.",
+ "x-trap=\"open\"",
+ "When the expression is truthy, traps focus within the element (useful for modals/dialogs)."
+ ),
+ "x-collapse" to DirectiveDoc(
+ "x-collapse",
+ "Animate show/hide with a height transition.",
+ "x-collapse",
+ "Smoothly animates the element's height when toggling visibility with x-show."
+ ),
+ "x-spread" to DirectiveDoc(
+ "x-spread",
+ "Deprecated. Use x-bind with an object instead.",
+ "x-spread=\"directives\"",
+ "This directive is deprecated in Alpine v3. Use object syntax with x-bind instead."
+ ),
+ )
+
+ override fun generateDoc(element: PsiElement?, originalElement: PsiElement?): String? {
+ val attributeName = resolveAttributeName(element, originalElement) ?: return null
+ return generateDirectiveDoc(attributeName)
+ }
+
+ override fun getQuickNavigateInfo(element: PsiElement?, originalElement: PsiElement?): String? {
+ val attributeName = resolveAttributeName(element, originalElement) ?: return null
+ val baseName = attributeName.substringBefore('.')
+ val doc = directiveDocs[baseName] ?: return null
+ return doc.summary
+ }
+
+ private fun resolveAttributeName(element: PsiElement?, originalElement: PsiElement?): String? {
+ val target = element ?: originalElement ?: return null
+
+ if (target is XmlTokenImpl && target.tokenType == XmlTokenType.XML_NAME) {
+ val attr = target.parent as? XmlAttribute ?: return null
+ val name = attr.name
+ if (name.startsWith("x-") || name.startsWith("@") || name.startsWith(":")) {
+ return name
+ }
+ }
+
+ if (target is XmlAttribute) {
+ val name = target.name
+ if (name.startsWith("x-") || name.startsWith("@") || name.startsWith(":")) {
+ return name
+ }
+ }
+
+ return null
+ }
+
+ private fun generateDirectiveDoc(attributeName: String): String? {
+ val baseName = when {
+ attributeName.startsWith("@") -> "x-on"
+ attributeName.startsWith(":") -> "x-bind"
+ else -> attributeName.substringBefore('.').substringBefore(':').let { base ->
+ if (directiveDocs.containsKey(attributeName.substringBefore('.'))) {
+ attributeName.substringBefore('.')
+ } else {
+ base
+ }
+ }
+ }
+
+ val doc = directiveDocs[baseName] ?: return null
+
+ return buildString {
+ append("")
+ append(doc.name)
+ append("${doc.summary}
") + append("Example: ${doc.example}
") + append("${doc.details}
") + append("