diff --git a/.mapping.json b/.mapping.json
index ec09869fa..27bc8fe1d 100644
--- a/.mapping.json
+++ b/.mapping.json
@@ -11835,6 +11835,25 @@
"client/ios/codegen_DivKit.sh":"divkit/public/client/ios/codegen_DivKit.sh",
"client/ios/codegen_Tests.sh":"divkit/public/client/ios/codegen_Tests.sh",
"client/web/divkit-examples/README.md":"divkit/public/client/web/divkit-examples/README.md",
+ "client/web/divkit-examples/custom-container/.npmrc":"divkit/public/client/web/divkit-examples/custom-container/.npmrc",
+ "client/web/divkit-examples/custom-container/README.md":"divkit/public/client/web/divkit-examples/custom-container/README.md",
+ "client/web/divkit-examples/custom-container/customElement.js":"divkit/public/client/web/divkit-examples/custom-container/customElement.js",
+ "client/web/divkit-examples/custom-container/index.html":"divkit/public/client/web/divkit-examples/custom-container/index.html",
+ "client/web/divkit-examples/custom-container/package-lock.json":"divkit/public/client/web/divkit-examples/custom-container/package-lock.json",
+ "client/web/divkit-examples/custom-container/package.json":"divkit/public/client/web/divkit-examples/custom-container/package.json",
+ "client/web/divkit-examples/custom-simple/.npmrc":"divkit/public/client/web/divkit-examples/custom-simple/.npmrc",
+ "client/web/divkit-examples/custom-simple/README.md":"divkit/public/client/web/divkit-examples/custom-simple/README.md",
+ "client/web/divkit-examples/custom-simple/customElement.js":"divkit/public/client/web/divkit-examples/custom-simple/customElement.js",
+ "client/web/divkit-examples/custom-simple/index.html":"divkit/public/client/web/divkit-examples/custom-simple/index.html",
+ "client/web/divkit-examples/custom-simple/package-lock.json":"divkit/public/client/web/divkit-examples/custom-simple/package-lock.json",
+ "client/web/divkit-examples/custom-simple/package.json":"divkit/public/client/web/divkit-examples/custom-simple/package.json",
+ "client/web/divkit-examples/custom-ssr/.npmrc":"divkit/public/client/web/divkit-examples/custom-ssr/.npmrc",
+ "client/web/divkit-examples/custom-ssr/README.md":"divkit/public/client/web/divkit-examples/custom-ssr/README.md",
+ "client/web/divkit-examples/custom-ssr/customElement.js":"divkit/public/client/web/divkit-examples/custom-ssr/customElement.js",
+ "client/web/divkit-examples/custom-ssr/package-lock.json":"divkit/public/client/web/divkit-examples/custom-ssr/package-lock.json",
+ "client/web/divkit-examples/custom-ssr/package.json":"divkit/public/client/web/divkit-examples/custom-ssr/package.json",
+ "client/web/divkit-examples/custom-ssr/server.js":"divkit/public/client/web/divkit-examples/custom-ssr/server.js",
+ "client/web/divkit-examples/custom-ssr/template.html":"divkit/public/client/web/divkit-examples/custom-ssr/template.html",
"client/web/divkit-examples/divkit-react/.npmrc":"divkit/public/client/web/divkit-examples/divkit-react/.npmrc",
"client/web/divkit-examples/divkit-react/README.md":"divkit/public/client/web/divkit-examples/divkit-react/README.md",
"client/web/divkit-examples/divkit-react/index.html":"divkit/public/client/web/divkit-examples/divkit-react/index.html",
@@ -11925,6 +11944,7 @@
"client/web/divkit/src/components/container/Container.svelte":"divkit/public/client/web/divkit/src/components/container/Container.svelte",
"client/web/divkit/src/components/container/ContainerSeparator.module.css":"divkit/public/client/web/divkit/src/components/container/ContainerSeparator.module.css",
"client/web/divkit/src/components/container/ContainerSeparators.svelte":"divkit/public/client/web/divkit/src/components/container/ContainerSeparators.svelte",
+ "client/web/divkit/src/components/custom/Custom.svelte":"divkit/public/client/web/divkit/src/components/custom/Custom.svelte",
"client/web/divkit/src/components/gallery/Gallery.module.css":"divkit/public/client/web/divkit/src/components/gallery/Gallery.module.css",
"client/web/divkit/src/components/gallery/Gallery.svelte":"divkit/public/client/web/divkit/src/components/gallery/Gallery.svelte",
"client/web/divkit/src/components/grid/Grid.module.css":"divkit/public/client/web/divkit/src/components/grid/Grid.module.css",
@@ -11969,6 +11989,7 @@
"client/web/divkit/src/context/state.ts":"divkit/public/client/web/divkit/src/context/state.ts",
"client/web/divkit/src/dev.html":"divkit/public/client/web/divkit/src/dev.html",
"client/web/divkit/src/dev.ts":"divkit/public/client/web/divkit/src/dev.ts",
+ "client/web/divkit/src/devCustomComponents.ts":"divkit/public/client/web/divkit/src/devCustomComponents.ts",
"client/web/divkit/src/expressions/ast.d.ts":"divkit/public/client/web/divkit/src/expressions/ast.d.ts",
"client/web/divkit/src/expressions/bigint.ts":"divkit/public/client/web/divkit/src/expressions/bigint.ts",
"client/web/divkit/src/expressions/const.ts":"divkit/public/client/web/divkit/src/expressions/const.ts",
@@ -12003,6 +12024,7 @@
"client/web/divkit/src/types/base.d.ts":"divkit/public/client/web/divkit/src/types/base.d.ts",
"client/web/divkit/src/types/border.d.ts":"divkit/public/client/web/divkit/src/types/border.d.ts",
"client/web/divkit/src/types/container.d.ts":"divkit/public/client/web/divkit/src/types/container.d.ts",
+ "client/web/divkit/src/types/custom.d.ts":"divkit/public/client/web/divkit/src/types/custom.d.ts",
"client/web/divkit/src/types/drawable.d.ts":"divkit/public/client/web/divkit/src/types/drawable.d.ts",
"client/web/divkit/src/types/edgeInserts.d.ts":"divkit/public/client/web/divkit/src/types/edgeInserts.d.ts",
"client/web/divkit/src/types/filter.d.ts":"divkit/public/client/web/divkit/src/types/filter.d.ts",
@@ -14164,6 +14186,7 @@
"client/web/divkit/typings/client-hydratable.d.ts":"divkit/public/client/web/divkit/typings/client-hydratable.d.ts",
"client/web/divkit/typings/client.d.ts":"divkit/public/client/web/divkit/typings/client.d.ts",
"client/web/divkit/typings/common.d.ts":"divkit/public/client/web/divkit/typings/common.d.ts",
+ "client/web/divkit/typings/custom.d.ts":"divkit/public/client/web/divkit/typings/custom.d.ts",
"client/web/divkit/typings/extensions.d.ts":"divkit/public/client/web/divkit/typings/extensions.d.ts",
"client/web/divkit/typings/server.d.ts":"divkit/public/client/web/divkit/typings/server.d.ts",
"client/web/divkit/typings/variables.d.ts":"divkit/public/client/web/divkit/typings/variables.d.ts",
diff --git a/client/web/divkit-examples/custom-container/.npmrc b/client/web/divkit-examples/custom-container/.npmrc
new file mode 100644
index 000000000..cffe8cdef
--- /dev/null
+++ b/client/web/divkit-examples/custom-container/.npmrc
@@ -0,0 +1 @@
+save-exact=true
diff --git a/client/web/divkit-examples/custom-container/README.md b/client/web/divkit-examples/custom-container/README.md
new file mode 100644
index 000000000..16e3233a4
--- /dev/null
+++ b/client/web/divkit-examples/custom-container/README.md
@@ -0,0 +1,42 @@
+## Using DivKit components inside your Custom Component
+
+[General notes about custom components](../../divkit/README.md#customComponents)
+
+[Previous example](../custom-simple)
+
+Install the packages first:
+
+```
+npm ci
+```
+
+And then open `index.html` file with any http server to see the result (the browser may block loading of external modules into the file:// pages).
+
+### How can support a DivKitComponents inside my Custom Component?
+
+First of all, lets look at the markup:
+
+```js
+ // DivJson
+ "type": "custom",
+ "custom_type": "custom_container",
+ "items": [
+ ...
+ ]
+```
+
+These `items` are the DivKit markup and will be rendered by DivKit itself.
+
+Now we will provide a place (`slot`) for these `items`:
+
+```js
+ // JS code
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.innerHTML = '';
+```
+
+This `` can be located at any depth of your element, not just a direct child of ShadowRoot.
+
+### How can I render Custom DivKit components on the Server Side?
+
+See [next example](../custom-ssr).
diff --git a/client/web/divkit-examples/custom-container/customElement.js b/client/web/divkit-examples/custom-container/customElement.js
new file mode 100644
index 000000000..357f99cbb
--- /dev/null
+++ b/client/web/divkit-examples/custom-container/customElement.js
@@ -0,0 +1,9 @@
+class CustomContainer extends HTMLElement {
+ constructor() {
+ super();
+
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.innerHTML = '';
+ }
+}
+customElements.define('custom-container', CustomContainer);
diff --git a/client/web/divkit-examples/custom-container/index.html b/client/web/divkit-examples/custom-container/index.html
new file mode 100644
index 000000000..7d2d1f9a0
--- /dev/null
+++ b/client/web/divkit-examples/custom-container/index.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/web/divkit-examples/custom-container/package-lock.json b/client/web/divkit-examples/custom-container/package-lock.json
new file mode 100644
index 000000000..a1851a256
--- /dev/null
+++ b/client/web/divkit-examples/custom-container/package-lock.json
@@ -0,0 +1,22 @@
+{
+ "name": "custom-simple",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "custom-simple",
+ "version": "1.0.0",
+ "license": "UNLICENSED",
+ "devDependencies": {
+ "@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
+ }
+ },
+ "node_modules/@divkitframework/divkit": {
+ "version": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb",
+ "resolved": "https://registry.npmjs.org/@divkitframework/divkit/-/divkit-28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb.tgz",
+ "integrity": "sha512-EEqRHNF77NYZhVsYGxQLEku9q5tYniAjFJ0UzsxM2TMwt6lLmJ96hOZbMro6wM7Fh6XuwC3Kjkc6PQdPTUuIXw==",
+ "dev": true
+ }
+ }
+}
diff --git a/client/web/divkit-examples/custom-container/package.json b/client/web/divkit-examples/custom-container/package.json
new file mode 100644
index 000000000..5bb3cdd9f
--- /dev/null
+++ b/client/web/divkit-examples/custom-container/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "custom-simple",
+ "private": true,
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {},
+ "keywords": [],
+ "author": "",
+ "license": "UNLICENSED",
+ "devDependencies": {
+ "@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
+ }
+}
diff --git a/client/web/divkit-examples/custom-simple/.npmrc b/client/web/divkit-examples/custom-simple/.npmrc
new file mode 100644
index 000000000..cffe8cdef
--- /dev/null
+++ b/client/web/divkit-examples/custom-simple/.npmrc
@@ -0,0 +1 @@
+save-exact=true
diff --git a/client/web/divkit-examples/custom-simple/README.md b/client/web/divkit-examples/custom-simple/README.md
new file mode 100644
index 000000000..83be45693
--- /dev/null
+++ b/client/web/divkit-examples/custom-simple/README.md
@@ -0,0 +1,94 @@
+## Using custom components
+
+[General notes about custom components](../../divkit/README.md#customComponents)
+
+Install the packages first:
+
+```
+npm ci
+```
+
+And then open `index.html` file with any http server to see the result (the browser may block loading of external modules into the file:// pages).
+
+### What's the idea?
+
+In json markup you can provide a special component:
+
+```json
+// DivJson
+{
+ "type": "custom",
+ "custom_type": "custom_card"
+}
+```
+
+Without any help, DivKit will not know about this special component. This can be done using the `customComponents` map:
+
+```js
+// JS code
+render({
+ id: 'test',
+ target: document.querySelector('#root'),
+ customComponents: new Map([
+ ['custom_card', {
+ element: 'custom-card'
+ }]
+ ]),
+ json: {
+ ...
+ }
+});
+```
+
+Now we need to create our `custom-card` component. This can be done using the power of Custom Elements:
+
+```js
+// JS Code
+class CustomCard extends HTMLElement {
+ constructor() {
+ super();
+
+ // some logic
+ }
+}
+customElements.define('custom-card', CustomCard);
+```
+
+This code defines a custom element inside the browser. So, any `custom` component inside DivKit should be a Custom Element in terms of HTML.
+
+### How can I provide additional data for a component?
+
+`custom` components in DivKit can accept an additional field `custom_props`. This is an object, and all its properties are directly mapped as attributes of your Custom Element:
+
+```js
+ // DivJson
+ "custom_props": {
+ "start": 15
+ }
+
+ // ->
+
+ // HTML
+
+```
+
+Keep in mind that there are no integer attributes here, all additional properties will be rendered as regular html attributes.
+
+Next, your Custom Element should read these properties:
+
+```js
+ // JS code
+ connectedCallback() {
+ const start = this.getAttribute('start');
+ }
+```
+
+Keep in mind that these additional attributes will only be available in the `connectedCallback`, and not in the `constructor` of the Custom Element.
+
+### What about browser support?
+
+Custom elements have been supported in all modern browsers for a while. There are some nuances that will be covered in the following examples, but basic support for Custom Elements v1 will be a good start for your components.
+
+### How can I wrap other DivKit components with my Custom Component?
+
+See [next example](../custom-container).
diff --git a/client/web/divkit-examples/custom-simple/customElement.js b/client/web/divkit-examples/custom-simple/customElement.js
new file mode 100644
index 000000000..c109bf6ad
--- /dev/null
+++ b/client/web/divkit-examples/custom-simple/customElement.js
@@ -0,0 +1,56 @@
+class CustomCard extends HTMLElement {
+ constructor() {
+ super();
+
+ const shadow = this.attachShadow({ mode: 'open' });
+
+ const style = document.createElement('style');
+
+ style.textContent = `
+ .component {
+ padding: 30px;
+ font-size: 20px;
+ background: linear-gradient(85deg, #fff, #068200, #1eea00, #FF0000, #fcfa1e, #ccd49b, #7af719);
+ }
+ `;
+
+ const div = this._text = document.createElement('div');
+ div.className = 'component';
+ div.textContent = '00:00';
+
+ shadow.appendChild(style);
+ shadow.appendChild(div);
+ }
+
+ _updateText() {
+ const minutes = Math.floor(this._counter / 60);
+ const seconds = this._counter % 60;
+ this._text.textContent = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
+ }
+
+ _tick() {
+ this._timeout = window.setTimeout(() => {
+ ++this._counter;
+
+ this._updateText();
+
+ this._tick();
+ }, 1000);
+ }
+
+ connectedCallback() {
+ const start = this.getAttribute('start');
+ this._counter = Number(start) || 0;
+ this._timeout = 0;
+ this._updateText();
+ this._tick();
+ }
+
+ disconnectedCallback() {
+ if (this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = 0;
+ }
+ }
+}
+customElements.define('custom-card', CustomCard);
diff --git a/client/web/divkit-examples/custom-simple/index.html b/client/web/divkit-examples/custom-simple/index.html
new file mode 100644
index 000000000..3325ed21a
--- /dev/null
+++ b/client/web/divkit-examples/custom-simple/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/web/divkit-examples/custom-simple/package-lock.json b/client/web/divkit-examples/custom-simple/package-lock.json
new file mode 100644
index 000000000..a1851a256
--- /dev/null
+++ b/client/web/divkit-examples/custom-simple/package-lock.json
@@ -0,0 +1,22 @@
+{
+ "name": "custom-simple",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "custom-simple",
+ "version": "1.0.0",
+ "license": "UNLICENSED",
+ "devDependencies": {
+ "@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
+ }
+ },
+ "node_modules/@divkitframework/divkit": {
+ "version": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb",
+ "resolved": "https://registry.npmjs.org/@divkitframework/divkit/-/divkit-28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb.tgz",
+ "integrity": "sha512-EEqRHNF77NYZhVsYGxQLEku9q5tYniAjFJ0UzsxM2TMwt6lLmJ96hOZbMro6wM7Fh6XuwC3Kjkc6PQdPTUuIXw==",
+ "dev": true
+ }
+ }
+}
diff --git a/client/web/divkit-examples/custom-simple/package.json b/client/web/divkit-examples/custom-simple/package.json
new file mode 100644
index 000000000..5bb3cdd9f
--- /dev/null
+++ b/client/web/divkit-examples/custom-simple/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "custom-simple",
+ "private": true,
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {},
+ "keywords": [],
+ "author": "",
+ "license": "UNLICENSED",
+ "devDependencies": {
+ "@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
+ }
+}
diff --git a/client/web/divkit-examples/custom-ssr/.npmrc b/client/web/divkit-examples/custom-ssr/.npmrc
new file mode 100644
index 000000000..cffe8cdef
--- /dev/null
+++ b/client/web/divkit-examples/custom-ssr/.npmrc
@@ -0,0 +1 @@
+save-exact=true
diff --git a/client/web/divkit-examples/custom-ssr/README.md b/client/web/divkit-examples/custom-ssr/README.md
new file mode 100644
index 000000000..8986ad09f
--- /dev/null
+++ b/client/web/divkit-examples/custom-ssr/README.md
@@ -0,0 +1,131 @@
+## Using DivKit components inside your Custom Component
+
+[General notes about custom components](../../divkit/README.md#customComponents)
+
+[Previous example with a simple component](../custom-simple)
+
+[Previous example with container component](../custom-container)
+
+[General info about SSR in DivKit](../ssr)
+
+This example assumes that you are familiar with the ideas from the previous examples.
+
+Install the packages first:
+
+```
+npm ci
+```
+
+### Run sample
+
+```shell
+npm start
+```
+
+### How does it work without items?
+
+The server code contains a similar `customComponents` map and will produce something like this:
+
+```html
+ // HTML
+
+```
+
+On the client side, this markup will be hydrated. So, what about markup inside the Custom Element?
+
+Lets assume that our component has some markup, for example, `Hello custom container`:
+
+```html
+ // HTML
+
+
+ Hello custom container
+
+
+```
+
+Custom elements is defined on the client side, and their markup is created in the browser. This means that if we don't do anything, our glorious SSR (Server-side rendering) will produce incomplete markup, which will be changed after the Custom Element is defined. The component will work as expected, but the user may notice a layout shift and a markup change.
+
+Declarative shadow dom comes to the rescue! If our `customComponents` contain not only the tag name of the Custom Element, but also a template itself, DivKit will produce a `` for this component for us:
+
+```js
+ // JS code
+ {
+ element: 'custom-container',
+ template: 'Hello custom container'
+ }
+
+ // HTML
+
+
+ Hello custom container
+
+
+```
+
+This markup will be parsed in the browser directly into the shadow dom:
+
+```html
+
+ #shadow-root (open)
+ Hello custom container
+
+```
+
+In this case, the browser will be able to show the exact page even without running JS on the client side, which means that there will be no layout shift on our page and it will be persisted during the entire page load.
+
+Don't forget to change your custom element code:
+
+```js
+ // JS Code
+ if (this.shadowRoot) {
+ // Declarative shadow dom is supported and parsed by the browser
+ } else {
+ // Create a shadow root as usual
+ const shadow = this.attachShadow({ mode: 'open' });
+ // ...
+ }
+```
+
+### How it works with items?
+
+Literally the same. Don't forget to include `` in your template, and everything will be fine:
+
+```js
+ // JS code
+ {
+ element: 'custom-container',
+ template: 'Hello custom container'
+ }
+
+ // HTML Server response
+
+
+ Hello custom container
+
+
+
+
+
+
+
+ // In the browser parsed HTML
+
+ #shadow-root (open)
+ Hello custom container
+
+ ↳
+