{"id":24983718,"url":"https://github.com/posthtml/posthtml-components","last_synced_at":"2025-04-11T20:52:51.861Z","repository":{"id":61839041,"uuid":"549368752","full_name":"posthtml/posthtml-components","owner":"posthtml","description":"A PostHTML plugin for creating components with HTML-friendly syntax inspired by Laravel Blade. Slots, stack/push, props, custom tag and much more.","archived":false,"fork":false,"pushed_at":"2025-04-01T11:12:14.000Z","size":1883,"stargazers_count":14,"open_issues_count":3,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-11T20:52:36.561Z","etag":null,"topics":["components","html","javascript","posthtml","posthtml-plugin","posthtml-plugins"],"latest_commit_sha":null,"homepage":"https://posthtml.github.io/posthtml-components/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/posthtml.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-11T04:39:52.000Z","updated_at":"2025-04-01T11:11:08.000Z","dependencies_parsed_at":"2023-11-08T03:52:55.997Z","dependency_job_id":"1ab76425-4637-415d-9c78-d5d5d423d2ea","html_url":"https://github.com/posthtml/posthtml-components","commit_stats":{"total_commits":212,"total_committers":4,"mean_commits":53.0,"dds":0.4528301886792453,"last_synced_commit":"ffd081a836fc40ee1ef66dc9d892fbc46a8994be"},"previous_names":["posthtml/posthtml-components","thewebartisan7/posthtml-components"],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posthtml%2Fposthtml-components","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posthtml%2Fposthtml-components/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posthtml%2Fposthtml-components/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/posthtml%2Fposthtml-components/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/posthtml","download_url":"https://codeload.github.com/posthtml/posthtml-components/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248480515,"owners_count":21110936,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["components","html","javascript","posthtml","posthtml-plugin","posthtml-plugins"],"created_at":"2025-02-04T09:20:34.682Z","updated_at":"2025-04-11T20:52:51.833Z","avatar_url":"https://github.com/posthtml.png","language":"HTML","readme":"[npm]: https://www.npmjs.com/package/posthtml-component\n[npm-version-shield]: https://img.shields.io/npm/v/posthtml-component.svg\n[npm-stats]: http://npm-stat.com/charts.html?package=posthtml-component\n[npm-stats-shield]: https://img.shields.io/npm/dt/posthtml-component.svg\n[github-ci]: https://github.com/posthtml/posthtml-components/actions/workflows/build.yml\n[github-ci-shield]: https://github.com/posthtml/posthtml-components/actions/workflows/build.yml/badge.svg\n[license]: ./LICENSE\n[license-shield]: https://img.shields.io/npm/l/posthtml-component.svg\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg width=\"150\" height=\"150\" alt=\"PostHTML\" src=\"https://posthtml.github.io/posthtml/logo.svg\"\u003e\n  \u003ch1\u003ePostHTML Components\u003c/h1\u003e\n  \u003cp\u003eLaravel Blade-inspired components for PostHTML\u003c/p\u003e\n\n  [![Version][npm-version-shield]][npm]\n  [![Build][github-ci-shield]][github-ci]\n  [![License][license-shield]][license]\n  [![Downloads][npm-stats-shield]][npm-stats]\n\u003c/div\u003e\n\n## Installation\n\n```bash\nnpm i -D posthtml-component\n```\n\n## Introduction\n\nThis PostHTML plugin provides an HTML-friendly syntax for using components in your HTML templates.\nIf you are familiar with Blade, React, Vue or similar, you will find the syntax to be familiar, as this plugin is inspired by them.\n\n**See also the first [PostHTML Bootstrap UI](https://github.com/thewebartisan7/posthtml-bootstrap-ui) using this plugin and check also the [starter template here](https://github.com/thewebartisan7/posthtml-bootstrap-ui-starter).**\n\n## Options\n\n| Name                     | Type               | Default                                      | Description                                                                      |\n|--------------------------|--------------------|----------------------------------------------|----------------------------------------------------------------------------------|\n| **root**                 | `String`           | `'./'`                                       | Root path where to look for components.                                          |\n| **folders**              | `String[]`         | `['']`                                       | Array of paths relative to `options.root` or defined namespaces.                 |\n| **fileExtension**        | `String\\|String[]` | `'html'`                                     | Component file extensions to look for.                                           |\n| **tagPrefix**            | `String`           | `'x-'`                                       | Tag prefix.                                                                      |\n| **tag**                  | `String\\|Boolean`  | `false`                                      | Component tag. Use with `options.attribute`. Boolean only `false`.               |\n| **attribute**            | `String`           | `'src'`                                      | Attribute to use for defining path to component file.                            |\n| **namespaces**           | `String[]`         | `[]`                                         | Array of namespace root paths, fallback paths, and custom override paths.        |\n| **namespaceSeparator**   | `String`           | `'::'`                                       | Namespace separator for tag names.                                               |\n| **yield**                | `String`           | `'yield'`                                    | Tag name for injecting main component content.                                   |\n| **slot**                 | `String`           | `'slot'`                                     | Tag name for [slots](#slots)                                                     |\n| **fill**                 | `String`           | `'fill'`                                     | Tag name for filling slots.                                                      |\n| **slotSeparator**        | `String`           | `':'`                                        | Name separator for `\u003cslot\u003e` and `\u003cfill\u003e` tags.                                   |\n| **stack**                | `String`           | `'stack'`                                    | Tag name for [`\u003cstack\u003e`](#stacks).                                               |\n| **push**                 | `String`           | `'push'`                                     | Tag name for `\u003cpush\u003e`.                                                           |\n| **propsScriptAttribute** | `String`           | `'props'`                                    | Attribute in `\u003cscript props\u003e` for retrieving [component props](#props).          |\n| **propsContext**         | `String`           | `'props'`                                    | Name of the object inside the script for processing props.                       |\n| **propsAttribute**       | `String`           | `'props'`                                    | Attribute name to define props as JSON on a component tag.                       |\n| **propsSlot**            | `String`           | `'props'`                                    | Used to retrieve props passed to slot via `$slots.slotName.props`.               |\n| **parserOptions**        | `Object`           | `{recognizeSelfClosing: true}`               | Pass options to `posthtml-parser`.                                               |\n| **expressions**          | `Object`           | `{}`                                         | Pass options to `posthtml-expressions`.                                          |\n| **plugins**              | `Array`            | `[]`                                         | PostHTML plugins to apply to every parsed component.                             |\n| **matcher**              | `Object`           | `[{tag: options.tagPrefix}]`                 | Array of objects used to match tags.                                             |\n| **attrsParserRules**     | `Object`           | `{}`                                         | Additional rules for attributes parser plugin.                                   |\n| **strict**               | `Boolean`          | `true`                                       | Toggle exception throwing.                                                       |\n| **mergeCustomizer**      | `Function`         | `function`                                   | Callback for lodash `mergeWith` to merge `options.expressions.locals` and props. |\n| **utilities**            | `Object`           | `{merge: _.mergeWith, template: _.template}` | Utility methods passed to `\u003cscript props\u003e`.                                      |\n| **elementAttributes**    | `Object`           | `{}`                                         | Object with tag names and function modifiers of `valid-attributes.js`.           |\n| **safelistAttributes**   | `String[]`         | `['data-*']`                                 | Array of attribute names to add to default valid attributes.                     |\n| **blocklistAttributes**  | `String[]`         | `[]`                                         | Array of attribute names to remove from default valid attributes.                |\n\n## Basic example\n\nCreate the component:\n\n``` html\n\u003c!-- src/button.html --\u003e\n\u003cbutton type=\"button\" class=\"btn\"\u003e\n  \u003cyield\u003e\u003c/yield\u003e\n\u003c/button\u003e\n```\n\nUse it:\n\n``` html\n\u003c!-- src/index.html --\u003e\n\u003chtml\u003e\n\u003cbody\u003e\n  \u003cx-button type=\"submit\" class=\"btn-primary\"\u003eSubmit\u003c/x-button\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nInit PostHTML:\n\n```js\n// index.js\nconst posthtml = require('posthtml')\nconst components = require('posthtml-component')\nconst { readFileSync, writeFileSync } = require('node:fs')\n\nposthtml([\n  components({ root: './src' })\n])\n  .process(readFileSync('src/index.html', 'utf8'))\n  .then(result =\u003e writeFileSync('dist/index.html', result.html, 'utf8'))\n```\n\nResult:\n\n``` html\n\u003c!-- dist/index.html --\u003e\n\u003chtml\u003e\n\u003cbody\u003e\n  \u003cbutton type=\"submit\" class=\"btn btn-primary\"\u003eSubmit\u003c/button\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nYou might have noticed that the `src/button.html` component contains `type` and `class` attributes, and that we also passed those attributes when we used it in `src/index.html`.\n\nThe result is that `type` was overridden, and `class` was merged.\n\nBy default, `class` and `style` attributes are merged, while all others attribute are overridden. You can also override `class` and `style` attributes by prepending `override:` to the class attribute. \n\nFor example:\n\n```html\n\u003cx-button override:class=\"btn-custom\"\u003eSubmit\u003c/x-button\u003e\n\n\u003c!-- Output --\u003e\n\u003cbutton type=\"button\" class=\"btn-custom\"\u003eSubmit\u003c/button\u003e\n```\n\nAll attributes that you pass to the component will be added to the first node of your component or to the node with an attribute named `attributes`, _only_ if they are not defined as `props` via `\u003cscript props\u003e` or if they are not \"known attributes\" (see\n[valid-attributes.js](https://github.com/thewebartisan7/posthtml-components/blob/main/src/valid-attributes.js)). \n\nYou can also define which attributes are considered to be valid, via the plugin's options.\n\nMore details on this in [Attributes](#attributes) section.\n\n### yield\n\nThe `\u003cyield\u003e` tag is where content that you pass to a component will be injected.\n\nThe plugin configures the PostHTML parser to recognize self-closing tags, so you can also just write is as `\u003cyield /\u003e`.\n\nFor brevity, we will use self-closing tags in the examples.\n\n### fileExtension\n\nBy default, the plugin looks for components with the `.html` extension. You can change this by passing an array of extensions to the `fileExtension` option.\n\nWhen using an array, if two files with the same name match both extensions, the file matching the first extension in the array will be used.\n\n```js\nconst posthtml = require('posthtml')\nconst components = require('posthtml-component')\n\nposthtml([\n  components({ \n    root: './src', // contains layout.html and layout.md\n    fileExtension: ['html', 'md']\n  })\n])\n  .process(`\u003cx-layout /\u003e`)\n  .then(result =\u003e console.log(result.html)) // layout.html content\n```\n\n### More examples\n\nSee also the `docs-src` folder where you can find more examples. \n\nYou can clone this repo and run `npm run build` to compile them.\n\n## Features\n\n### Tag names and x-tags\n\nYou can use the components in multiple ways, or also a combination of them.\n\nIf you want to use components as 'includes', you can define tag and `src` attribute names.\n\nUsing our previous button component example, we can define the tag and attribute names and then use it like this:\n\n```hbs\n\u003c!-- src/index.html --\u003e\n\u003chtml\u003e\n  \u003cbody\u003e\n    \u003ccomponent src=\"button.html\"\u003eSubmit\u003c/component\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nInit PostHTML:\n\n```js\n// index.js\n\nrequire('posthtml')(\n  require('posthtml-component')({ \n    root: './src', \n    tag: 'component', \n    attribute: 'src' \n  }))\n  .process(/* ... */)\n  .then(/* ... */)\n```\n\nIf you need more control over tag matching, you may pass an array of matcher or single object via `options.matcher`:\n\n```js\n// index.js\nconst options = { \n  root: './src', \n  matcher: [\n    {tag: 'a-tag'}, \n    {tag: 'another-one'}, \n    {tag: new RegExp(`^app-`, 'i')},\n  ] \n};\n\nrequire('posthtml')(require('posthtml-component')(options))\n  .process(/* ... */)\n  .then(/* ... */)\n```\n\nWith `posthtml-components` you don't need to specify the path name when you are using `x-tag-name` syntax.\n\nSetup PostHTML:\n\n```js\n// index.js\nconst options = { \n  root: './src', \n  tagPrefix: 'x-'\n};\n\nrequire('posthtml')(require('posthtml-component')(options))\n  .process(/* ... */)\n  .then(/* ... */)\n```\n\nUse:\n\n``` html\n\u003c!-- src/index.html --\u003e\n\u003chtml\u003e\n\u003cbody\u003e\n  \u003cx-button\u003eSubmit\u003c/x-button\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nIf your components are in a subfolder then you can use `dot` to access it:\n\n``` html\n\u003c!-- src/components/forms/button.html --\u003e\n\u003cx-forms.button\u003eSubmit\u003c/x-forms.button\u003e\n```\n\nIf your components are in a sub-folder with multiple files, then in order to avoid writing out the main file name you can use `index.html` without specifying it.\n\nHere's an example:\n\n``` html\n\u003c!-- src/components/modals/index.html --\u003e\n\u003cx-modal.index\u003eSubmit\u003c/x-modal.index\u003e\n\n\u003c!-- You may omit \"index\" part since the file is named \"index.html\" --\u003e\n\u003cx-modal\u003eSubmit\u003c/x-modal\u003e\n```\n\n#### Parser options\n\nYou may pass options to `posthtml-parser` via `options.parserOptions`.\n\n```js\n// index.js\nconst options = { \n  root: './src', \n  parserOptions: { decodeEntities: true } \n};\n\nrequire('posthtml')(require('posthtml-component')(options))\n  .process('some HTML', options.parserOptions)\n  .then(/* ... */)\n```\n\n\u003e [!IMPORTANT]\n\u003e The `parserOptions` that you pass to the plugin must also be passed in the `process` method in your code, otherwise your PostHTML build will use `posthtml-parser` defaults and will override anything you've passed to `posthtml-component`.\n\n#### Self-closing tags\n\nThe plugin supports self-closing tags by default, but you need to make sure to enable them in the `process` method in your code too, by passing `recognizeSelfClosing: true` in the options object:\n\n```js\n// index.js\nrequire('posthtml')(require('posthtml-component')({root: './src'}))\n  .process('your HTML...', {recognizeSelfClosing: true})\n  .then(/* ... */)\n```\n\nIf you don't add this to `process`, PostHTML will use `posthtml-parser` defaults and will not support self-closing component tags. This will result in everything after a self-closing tag not being output.\n\n### Multiple folders\n\nYou have full control over where your component files exist. Once you set the base root path of your components, you can then set multiple folders.\n\nFor example if your root is `./src` and then you have several folders where you have your components, for example `./src/components` and `./src/layouts`, you can set up the plugin like below:\n\n```js\n// index.js\nconst options = { \n  root: './src', \n  folders: ['components', 'layouts'] \n};\n\nrequire('posthtml')(require('posthtml-component')(options))\n  .process(/* ... */)\n  .then(/* ... */)\n```\n\n### Namespaces\n\nWith namespaces, you can define a top level root path to your components.\n\nIt can be useful for handling custom themes, where you define a specific top level root with a fallback root for when a component is not found, and a custom root for overriding.\n\nThis makes it possible to create folder structures like this:\n\n- `src` (root folder)\n  - `components` (folder for components like modal, button, etc.)\n  - `layouts` (folder for layout components like base layout, header, footer, etc.)\n  - `theme-dark` (namespace folder for theme-dark)\n    - `components` (folder for components for theme dark)\n    - `layouts` (folder for layout components for dark theme)\n  - `theme-light` (namespace folder for theme-light)\n    - `components` (folder for components for light theme)\n    - `layouts` (folder for layout components for dark theme)\n  - `custom` (custom folder for override your namespace themes)\n    - `theme-dark` (custom folder for override dark theme)\n      - `components` (folder for override components of theme dark)\n      - `layouts` (folder for override layout components of dark theme)\n    - `theme-light` (custom folder for override light theme)\n      - `components` (folder for override components of theme dark)\n      - `layouts` (folder for override layout components of dark theme)\n\nAnd the options would be like:\n\n```js\n// index.js\nconst options = {\n  // Root for component without namespace\n  root: './src',\n  // Folders is always appended in 'root' or any defined namespace's folders (base, fallback or custom)\n  folders: ['components', 'layouts'],\n  namespaces: [{\n    // Namespace name will be prepended to tag name (example \u003cx-theme-dark::button\u003e)\n    name: 'theme-dark',\n    // Root of the namespace\n    root: './src/theme-dark',\n    // Fallback root when a component is not found in namespace\n    fallback: './src',\n    // Custom root for overriding, the lookup happens here first\n    custom: './src/custom/theme-dark'\n  }, {\n    // Light theme\n    name: 'theme-light',\n    root: './src/theme-light',\n    fallback: './src',\n    custom: './src/custom/theme-light'\n  }, {\n    /* ... */\n  }]\n};\n```\n\nUse the component namespace:\n\n```xml\n\u003c!-- src/index.html --\u003e\n\u003chtml\u003e\n\u003cbody\u003e\n  \u003cx-theme-dark::button\u003eSubmit\u003c/theme-dark::button\u003e\n  \u003cx-theme-light::button\u003eSubmit\u003c/theme-light::button\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Slots\n\nComponents may define slots that can be filled with content when used.\n\nFor example:\n\n```xml\n\u003c!-- src/modal.html --\u003e\n\u003cdiv class=\"modal\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    \u003cslot:header /\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    \u003cslot:body /\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-footer\"\u003e\n    \u003cslot:footer /\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nUse the component:\n\n```xml\n\u003c!-- src/index.html --\u003e\n\u003cx-modal\u003e\n  \u003cfill:header\u003eHeader content\u003c/fill:header\u003e\n  \u003cfill:body\u003eBody content\u003c/fill:body\u003e\n  \u003cfill:footer\u003eFooter content\u003c/fill:footer\u003e\n\u003c/x-modal\u003e\n```\n\nResult:\n\n```html\n\u003c!-- dist/index.html --\u003e\n\u003cdiv class=\"modal\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    Header content\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    Body content\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-footer\"\u003e\n    Footer content\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nBy default, the slot content is replaced, but you can also prepend or append the content, or keep the default content by not filling the slot.\n\nAdd some default content in the component:\n\n```xml\n\u003c!-- src/modal.html --\u003e\n\u003cdiv class=\"modal\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    \u003cslot:header\u003eDefault header\u003c/slot:header\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    \u003cslot:body\u003econtent\u003c/slot:body\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-footer\"\u003e\n    \u003cslot:footer\u003eFooter\u003c/slot:footer\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n```xml\n\u003c!-- src/index.html --\u003e\n\u003cx-modal\u003e\n  \u003cfill:body prepend\u003ePrepend body\u003c/fill:body\u003e\n  \u003cfill:footer append\u003econtent\u003c/fill:footer\u003e\n\u003c/x-modal\u003e\n```\n\nResult:\n\n```html\n\u003c!-- dist/index.html --\u003e\n\u003cdiv class=\"modal\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    Default header\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    Prepend body content\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-footer\"\u003e\n    Footer content\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n### Stacks\n\nYou may push content to named stacks which can be rendered somewhere else, like in another component. This can be particularly useful for specifying any JavaScript or CSS required by your components.\n\nFirst, add a `\u003cstack\u003e` tag to your HTML:\n\n```diff\n\u003c!-- src/index.html --\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n+    \u003cstack name=\"styles\" /\u003e\n  \u003c/head\u003e\n\u003cbody\u003e\n  \u003cx-modal\u003e\n    \u003cfill:header\u003eHeader content\u003c/fill:header\u003e\n    \u003cfill:body\u003eBody content\u003c/fill:body\u003e\n    \u003cfill:footer\u003eFooter content\u003c/fill:footer\u003e\n  \u003c/x-modal\u003e\n  \n+  \u003cstack name=\"scripts\" /\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThen, in modal components or any other child components, you can push content to this stack:\n\n```xml\n\u003c!-- src/modal.html --\u003e\n\u003cdiv class=\"modal\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    \u003cslot:header /\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    \u003cslot:body /\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-footer\"\u003e\n    \u003cslot:footer /\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n\u003cpush name=\"styles\"\u003e\n  \u003clink href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"\u003e\n\u003c/push\u003e\n\n\u003cpush name=\"scripts\"\u003e\n  \u003cscript src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js\"\u003e\u003c/script\u003e\n\u003c/push\u003e\n```\n\nThe output will be:\n\n```html\n\u003c!-- dist/index.html --\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003clink href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cdiv class=\"modal\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    Header content\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    Body content\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-footer\"\u003e\n    Footer content\n  \u003c/div\u003e\n\u003c/div\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThe `once` attribute allows you to push content only once per rendering cycle. \n\nFor example, if you are rendering a given component within a loop, you may wish to only push the JavaScript and CSS the first time the component is rendered.\n\nExample.\n\n```html\n\u003c!-- src/modal.html --\u003e\n\u003cdiv class=\"modal\"\u003e\n  \u003c!-- ... --\u003e\n\u003c/div\u003e\n\n\u003c!-- The push content will be pushed only once in the stack --\u003e\n\n\u003cpush name=\"styles\" once\u003e\n  \u003clink href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"\u003e\n\u003c/push\u003e\n\n\u003cpush name=\"scripts\" once\u003e\n  \u003cscript src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js\"\u003e\u003c/script\u003e\n\u003c/push\u003e\n```\n\nBy default, the content is pushed in the stack in the given order. If you would like to prepend content onto the beginning of a stack, you may use the `prepend` attribute:\n\n```html\n\u003cpush name=\"scripts\"\u003e\n  \u003c!-- This will be second --\u003e\n  \u003cscript src=\"/example.js\"\u003e\u003c/script\u003e\n\u003c/push\u003e\n\n\u003c!-- Later... --\u003e\n\u003cpush name=\"scripts\" prepend\u003e\n  \u003c!-- This will be first --\u003e\n  \u003cscript src=\"/example-2.js\"\u003e\u003c/script\u003e\n\u003c/push\u003e\n```\n\n### Props\n\n`props` can be passed to components in HTML attributes. To use them in a component, they must be defined in the component's `\u003cscript props\u003e` tag.\n\nFor example:\n\n```html\n\u003c!-- src/alert.html --\u003e\n\u003cscript props\u003e\n  module.exports = {\n    title: props.title || 'Default title'\n  }\n\u003c/script\u003e\n\u003cdiv\u003e\n  {{ title }}\n\u003c/div\u003e\n```\n\nUse:\n\n```html\n\u003cx-alert title=\"Hello world!\" /\u003e\n```\n\nThe output will be:\n\n```html\n\u003cdiv\u003e\n  Hello world!\n\u003c/div\u003e\n```\n\nIf no `title` attribute is passed to the component, the default value will be used.\n\n```html\n\u003cx-my-alert /\u003e\n```\n\nThe output will be:\n\n```html\n\u003cdiv\u003e\n  Default title\n\u003c/div\u003e\n```\n\nInside `\u003cscript props\u003e` you have access to passed props via an object named `props`.\n\nHere's an example of what you can do with it:\n\n```html\n\u003c!-- src/modal.html --\u003e\n\u003cscript props\u003e\n  module.exports = {\n    title: props.title || 'Default title', \n    size: props.size ? `modal-${props.size}` : '', \n    items: Array.isArray(props.items) ? props.items.concat(['first', 'second']) : ['first', 'second']\n  }\n\u003c/script\u003e\n\u003cdiv class=\"modal {{ size }}\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    {{ title }}\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    \u003ceach loop=\"item in items\"\u003e\u003cspan\u003e{{ item }}\u003c/span\u003e\u003c/each\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nUse:\n\n```html\n\u003cx-modal \n  size=\"xl\" \n  title=\"My modal title\" \n  items='[\"third\", \"fourth\"]' \n  class=\"modal-custom\" \n/\u003e\n```\n\nThe output will be:\n\n```html\n\u003cdiv class=\"modal modal-custom modal-xl\"\u003e\n  \u003cdiv class=\"modal-header\"\u003e\n    My modal title\n  \u003c/div\u003e\n  \u003cdiv class=\"modal-body\"\u003e\n    \u003cspan\u003efirst\u003c/span\u003e\n    \u003cspan\u003esecond\u003c/span\u003e\n    \u003cspan\u003ethird\u003c/span\u003e\n    \u003cspan\u003efourth\u003c/span\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nNotice how the `class` attribute that we passed to the component is merged with `class` attribute value of the first node inside of it.\n\nYou can change how attributes are merged with global props defined via options, by passing a callback function.\n\nBy default, all props are scoped to the component, and are not available to nested components. You can however change this accordingly to your need.\n\nCreate a component:\n\n```html\n\u003c!-- src/child.html --\u003e\n\u003cscript props\u003e\n  module.exports = {\n    title: props.title || 'Default title'\n  }\n\u003c/script\u003e\n\u003cdiv\u003e\n  Prop in child: {{ title }}\n\u003c/div\u003e\n```\n\nCreate a `\u003cx-parent\u003e` component that uses `\u003cx-child\u003e`:\n\n```html\n\u003c!-- src/parent.html --\u003e\n\u003cscript props\u003e\n  module.exports = {\n    title: props.title || 'Default title'\n  }\n\u003c/script\u003e\n\u003cdiv\u003e\n  Prop in parent: {{ title }}\n  \u003cx-child /\u003e\n\u003c/div\u003e\n```\n\nUse it:\n\n```html\n\u003cx-parent title=\"My title\" /\u003e\n```\n\nThe output will be:\n\n```html\n\u003cdiv\u003e\n  Prop in parent: My title\n  \u003cdiv\u003e\n    Prop in child: Default title\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nAs you can see, `title` in `\u003cx-child\u003e` component renders the default value and not the one set via `\u003cx-parent\u003e`. \n\nTo change this, we must prepend `aware:` to the attribute name in order to pass the props to nested components.\n\n```html\n\u003cx-parent aware:title=\"My title\" /\u003e\n```\n\nThe output now will be:\n\n```html\n\u003cdiv\u003e\n  Prop in parent: My title\n  \u003cdiv\u003e\n    Prop in child: My title\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n### Attributes\n\nYou can pass any attributes to your components and they will be added to the first node of your component,\nor to the node with an attribute named `attributes`. \n\nIf you are familiar with Vue.js, this is the same as so-called\n[fallthrough attribute](https://vuejs.org/guide/components/attrs.html). Or, with Laravel Blade, it's\n[component-attributes](https://laravel.com/docs/11.x/blade#component-attributes).\n\nBy default, `class` and `style` are merged with existing `class` and `style` attribute. All other attributes are overridden by default.\n\nIf you pass an attribute that is defined as a `prop`, it will not be added to the component's node.\n\nHere's an example:\n\n```html\n\u003c!-- src/button.html --\u003e\n\u003cscript props\u003e\n  module.exports = {\n    label: props.label || 'A button'\n  }\n\u003c/script\u003e\n\u003cbutton type=\"button\" class=\"btn\"\u003e\n  {{ label }}\n\u003c/button\u003e\n```\n\nUse the component:\n\n```html\n\u003c!-- src/index.html --\u003e\n\u003cx-button type=\"submit\" class=\"btn-primary\" label=\"My button\" /\u003e\n```\n\nResult:\n\n```html\n\u003c!-- dist/index.html --\u003e\n\u003cbutton type=\"submit\" class=\"btn btn-primary\"\u003eMy button\u003c/button\u003e\n```\n\nIf you need to override `class` and `style` attribute values (instead of merging them), just prepend `override:` to the attribute name:\n\n```html\n\u003c!-- src/index.html --\u003e\n\u003cx-button type=\"submit\" override:class=\"btn-custom\" label=\"My button\" /\u003e\n```\n\nResult:\n\n```html\n\u003c!-- dist/index.html --\u003e\n\u003cbutton type=\"submit\" class=\"btn-custom\"\u003eMy button\u003c/button\u003e\n```\n\nIf you want the attributes to be passed to a certain node, use the `attributes` attribute:\n\n```html\n\u003c!-- src/my-component.html --\u003e\n\u003cdiv class=\"first-node\"\u003e\n  \u003cdiv class=\"second-node\" attributes\u003e\n    Hello world!\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nUse the component:\n\n```html\n\u003c!-- src/index.html --\u003e\n\u003cx-my-component class=\"my-class\" /\u003e\n```\n\nResult:\n\n```html\n\u003c!-- dist/index.html --\u003e\n\u003cdiv class=\"first-node\"\u003e\n  \u003cdiv class=\"second-node my-class\"\u003e\n    Hello world!\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nYou can add custom rules to define how attributes are parsed - we use [posthtml-attrs-parser](https://github.com/posthtml/posthtml-attrs-parser) to handle them.\n\n### Advanced attributes configurations\n\nIf default configurations for valid attributes are not right for you, you may configure them as explained below.\n\n```js\n// index.js\nconst { readFileSync, writeFileSync } = require('fs')\n\nconst posthtml = require('posthtml')\nconst components = require('posthtml-component')\n\nconst options = {\n  root: './src',  \n  // Add attributes to specific tag or override defaults\n  elementAttributes: {\n    DIV: (defaultAttributes) =\u003e {\n      /* Add new one */\n      defaultAttributes.push('custom-attribute-name');\n\n      return defaultAttributes;\n    },\n    DIV: (defaultAttributes) =\u003e {\n      /* Override all */\n      defaultAttributes = ['custom-attribute-name', 'another-one'];\n\n      return defaultAttributes;\n    },\n  },\n  \n  // Add attributes to all tags, use '*' as wildcard for attribute name that starts with\n  safelistAttributes: [\n    'custom-attribute-name',\n    'attribute-name-start-with-*'\n  ],\n\n  // Remove attributes from all tags that support it\n  blocklistAttributes: [\n    'role'\n  ]\n}\n\nposthtml(components(options))\n  .process(readFileSync('src/index.html', 'utf8'))\n  .then(result =\u003e writeFileSync('dist/index.html', result.html, 'utf8'))\n```\n\n## Examples\n\nYou can work with `\u003cslot\u003e` and `\u003cfill\u003e` or you can create component for each block of your component, and you can also support both of them.\n\nYou can find an example of this inside `docs-src/components/modal`. Following is a short explanation of both approaches.\n\n### Using slots\n\nLet's suppose we want to create a component for [bootstrap modal](https://getbootstrap.com/docs/5.2/components/modal/). \n\nThe code required is:\n\n```html\n\u003c!-- Modal HTML --\u003e\n\u003cdiv class=\"modal fade\" id=\"exampleModal\" tabindex=\"-1\" aria-labelledby=\"exampleModalLabel\" aria-hidden=\"true\"\u003e\n  \u003cdiv class=\"modal-dialog\"\u003e\n    \u003cdiv class=\"modal-content\"\u003e\n      \u003cdiv class=\"modal-header\"\u003e\n        \u003ch1 class=\"modal-title fs-5\" id=\"exampleModalLabel\"\u003eModal title\u003c/h1\u003e\n        \u003cbutton type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"\u003e\u003c/button\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"modal-body\"\u003e\n        ...\n      \u003c/div\u003e\n      \u003cdiv class=\"modal-footer\"\u003e\n        \u003cbutton type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\"\u003eClose\u003c/button\u003e\n        \u003cbutton type=\"button\" class=\"btn btn-primary\"\u003eSave changes\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nThere is almost three block of code: the header, the body and the footer.\n\nSo we could create a component with three slots:\n\n```diff\n\u003c!-- Modal component --\u003e\n\u003cdiv class=\"modal fade\" tabindex=\"-1\" aria-hidden=\"true\"\u003e\n  \u003cdiv class=\"modal-dialog\"\u003e\n    \u003cdiv class=\"modal-content\"\u003e\n      \u003cdiv class=\"modal-header\"\u003e\n-        \u003ch1 class=\"modal-title fs-5\" id=\"exampleModalLabel\"\u003eModal title\u003c/h1\u003e\n+        \u003cslot:header /\u003e\n        \u003cbutton type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"\u003e\u003c/button\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"modal-body\"\u003e\n+        \u003cslot:body /\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"modal-footer\"\u003e\n        \u003cbutton type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\"\u003eClose\u003c/button\u003e\n+        \u003cslot:footer /\u003e\n-        \u003cbutton type=\"button\" class=\"btn btn-primary\"\u003eSave changes\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nWe can then use it like this:\n\n```html\n \u003cx-modal\n  id=\"exampleModal\"\n  aria-labelledby=\"exampleModalLabel\"\n\u003e\n  \u003cslot:header\u003e\n    \u003ch5 class=\"modal-title\" id=\"exampleModalLabel\"\u003eMy modal\u003c/h5\u003e\n  \u003c/slot:header\u003e\n\n  \u003cslot:body\u003e\n    Modal body content goes here...\n  \u003c/slot:body\u003e\n\n  \u003cslot:footer close=\"false\"\u003e\n    \u003cbutton type=\"button\" class=\"btn btn-primary\"\u003eConfirm\u003c/button\u003e\n  \u003c/slot:footer\u003e\n\u003c/x-modal\u003e\n```\n\n### Splitting component in small component\n\nAnother approach is to split the component in smaller components, passing attributes to each of them.\n\nSo we create a main component and then three different smaller components:\n\n```html\n\u003c!-- Main modal component --\u003e\n\u003cdiv class=\"modal fade\" tabindex=\"-1\" aria-hidden=\"true\"\u003e\n  \u003cdiv class=\"modal-dialog\"\u003e\n    \u003cdiv class=\"modal-content\"\u003e\n      \u003cyield /\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n```html\n\u003c!-- Header modal component --\u003e\n\u003cdiv class=\"modal-header\"\u003e\n  \u003cyield /\u003e\n\u003c/div\u003e\n```\n\n```html\n\u003c!-- Body modal component --\u003e\n\u003cdiv class=\"modal-body\"\u003e\n  \u003cyield /\u003e\n\u003c/div\u003e\n```\n\n```html\n\u003c!-- Footer modal component --\u003e\n\u003cdiv class=\"modal-footer\"\u003e\n  \u003cyield /\u003e\n\u003c/div\u003e\n```\n\nAnd then you can use it like this:\n\n```html\n\u003cx-modal\n  id=\"exampleModal\"\n  aria-labelledby=\"exampleModalLabel\"\n\u003e\n  \u003cx-modal.header\u003e\n    \u003ch5 class=\"modal-title\" id=\"exampleModalLabel\"\u003eMy modal\u003c/h5\u003e\n  \u003c/x-modal.header\u003e\n\n  \u003cx-modal.body\u003e\n    Modal body content goes here...\n  \u003c/x-modal.body\u003e\n\n  \u003cx-modal.footer\u003e\n    \u003cbutton type=\"button\" class=\"btn btn-primary\"\u003eConfirm\u003c/button\u003e\n  \u003c/x-modal.footer\u003e\n\u003c/x-modal\u003e\n```\n\nAs said in this way you can pass attributes to each of them, without defining props.\n\n### Combine slots and small component\n\nYou can also combine both approaches, and then use them with slots or with small components:\n\n```html\n\n\u003c!-- Modal --\u003e\n\u003cdiv\n  class=\"modal fade\"\n  tabindex=\"-1\"\n  aria-hidden=\"true\"\n  aria-modal=\"true\"\n  role=\"dialog\"\n\u003e\n  \u003cdiv class=\"modal-dialog\"\u003e\n    \u003cdiv class=\"modal-content\"\u003e\n      \u003cif condition=\"$slots.header?.filled\"\u003e\n          \u003cx-modal.header\u003e\n            \u003cslot:header /\u003e\n          \u003c/x-modal.header\u003e\n      \u003c/if\u003e\n      \u003cif condition=\"$slots.body?.filled\"\u003e\n          \u003cx-modal.body\u003e\n              \u003cslot:body /\u003e\n          \u003c/x-modal.body\u003e\n      \u003c/if\u003e\n      \u003cif condition=\"$slots.footer?.filled\"\u003e\n          \u003cx-modal.footer close=\"{{ $slots.footer?.props.close }}\"\u003e\n              \u003cslot:footer /\u003e\n          \u003c/x-modal.footer\u003e\n      \u003c/if\u003e\n      \u003cyield /\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\u003c!-- /.modal-dialog --\u003e\n\u003c/div\u003e\u003c!-- /.modal --\u003e\n```\n\nNow you can use your component with slots or with small components.\n\nAs you may notice, by using slots, you already can use also your small components, and so you can also pass props\nvia `$slots` which has all the `props` passed via slot, and as well check if slot is filled.\n\n## Migration\n\nIf you are migrating from `posthtml-extend` and/or `posthtml-modules` please to follow updates here:\n[posthtml-components/issues/16](https://github.com/thewebartisan7/posthtml-components/issues/16).\n\n## Contributing\n\nSee [PostHTML Guidelines](https://github.com/posthtml/posthtml/tree/master/docs) and [contribution guide](CONTRIBUTING.md).\n\n## Credits\n\nThanks to all PostHTML contributors and especially to `posthtml-extend` and `posthtml-modules` contributors, as part of code is ~~stolen~~ inspired from these plugins.\nHuge thanks also to Laravel Blade template engine.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fposthtml%2Fposthtml-components","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fposthtml%2Fposthtml-components","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fposthtml%2Fposthtml-components/lists"}