diff --git a/examples/README.md b/examples/README.md
index 532f3702..0fbbf946 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -7,24 +7,26 @@ Here you can find examples written using _bau_.
[Frontend Mentor](https://frontendmentor.io) is an online platform that provides real-world front-end development challenges designed to help developers practice and improve their coding skills. It offers a wide range of project-based challenges, from beginner to advanced levels, that simulate real-world design-to-code scenarios.
Below is a list of projects implemented using _Bau_.
-| Name | Live | Code |
+
+| Name | Live | Code |
| ----------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------- |
-| Bento Grid | [live](https://grucloud.github.io/bau/frontendmentor/bento-grid/) | [code](./bento-grid) |
-| Blog Preview Card | [live](https://grucloud.github.io/bau/frontendmentor/blog-preview-card/) | [code](./blog-preview-card) |
-| Calculator | [live](https://grucloud.github.io/bau/frontendmentor/calculator/) | [code](./calculator) |
-| Contact Form | [live](https://grucloud.github.io/bau/frontendmentor/contact-form/) | [code](./contact-form) |
-| E-commerce Product Page | [live](https://grucloud.github.io/bau/frontendmentor/e-commerce-product-page/) | [code](./e-commerce-product-page) |
-| Faq Accordion | [live](https://grucloud.github.io/bau/frontendmentor/faq-accordion/) | [code](./faq-accordion) |
-| Interactive Comments Section | [live](https://grucloud.github.io/bau/frontendmentor/interactive-comments-section/) | [code](./interactive-comments-section) |
-| Interactive Rating Component | [live](https://grucloud.github.io/bau/frontendmentor/interactive-rating-component/) | [code](./interactive-rating-component) |
-| IP Address Tracker | [live](https://grucloud.github.io/bau/frontendmentor/ip-address-tracker/) | [code](./ip-address-tracker) |
-| Job Listing With Filtering | [live](https://grucloud.github.io/bau/frontendmentor/job-listings-with-filtering/) | [code](./job-listings-with-filtering) |
-| Launch Countdown Timer | [live](https://grucloud.github.io/bau/frontendmentor/launch-countdown-timer/) | [code](./launch-countdown-timer) |
+| Bento Grid | [live](https://grucloud.github.io/bau/frontendmentor/bento-grid/) | [code](./bento-grid) |
+| Blog Preview Card | [live](https://grucloud.github.io/bau/frontendmentor/blog-preview-card/) | [code](./blog-preview-card) |
+| Calculator | [live](https://grucloud.github.io/bau/frontendmentor/calculator/) | [code](./calculator) |
+| Contact Form | [live](https://grucloud.github.io/bau/frontendmentor/contact-form/) | [code](./contact-form) |
+| E-commerce Product Page | [live](https://grucloud.github.io/bau/frontendmentor/e-commerce-product-page/) | [code](./e-commerce-product-page) |
+| Faq Accordion | [live](https://grucloud.github.io/bau/frontendmentor/faq-accordion/) | [code](./faq-accordion) |
+| Interactive Comments Section | [live](https://grucloud.github.io/bau/frontendmentor/interactive-comments-section/) | [code](./interactive-comments-section) |
+| Interactive Rating Component | [live](https://grucloud.github.io/bau/frontendmentor/interactive-rating-component/) | [code](./interactive-rating-component) |
+| IP Address Tracker | [live](https://grucloud.github.io/bau/frontendmentor/ip-address-tracker/) | [code](./ip-address-tracker) |
+| Job Listing With Filtering | [live](https://grucloud.github.io/bau/frontendmentor/job-listings-with-filtering/) | [code](./job-listings-with-filtering) |
+| Launch Countdown Timer | [live](https://grucloud.github.io/bau/frontendmentor/launch-countdown-timer/) | [code](./launch-countdown-timer) |
+| Newsletter Signup Form | [live](https://grucloud.github.io/bau/frontendmentor/newsletter-signup-form/) | [code](./newsletter-signup-form) |
| Mortgage Repayment Calculator | [live](https://grucloud.github.io/bau/frontendmentor/mortgage-repayment-calculator/) | [code](./mortgage-repayment-calculator) |
-| Multi Step Form | [live](https://grucloud.github.io/bau/frontendmentor/multi-step-form/) | [code](./multi-step-form) |
-| Product List Cart | [live](https://grucloud.github.io/bau/frontendmentor/product-list-cart/) | [code](./product-list-cart) |
-| Product Preview Card | [live](https://grucloud.github.io/bau/frontendmentor/product-preview-card/) | [code](./product-preview-card) |
-| Recipe Page | [live](https://grucloud.github.io/bau/frontendmentor/recipePage/) | [code](./recipePage) |
-| Rest Countries | [live](https://grucloud.github.io/bau/frontendmentor/rest-countries/) | [code](./rest-countries) |
-| Result Summary Component | [live](https://grucloud.github.io/bau/frontendmentor/result-summary-component/) | [code](./result-summary-component) |
-| Social Link Profile | [live](https://grucloud.github.io/bau/frontendmentor/social-link-profile/) | [code](./social-link-profile) |
+| Multi Step Form | [live](https://grucloud.github.io/bau/frontendmentor/multi-step-form/) | [code](./multi-step-form) |
+| Product List Cart | [live](https://grucloud.github.io/bau/frontendmentor/product-list-cart/) | [code](./product-list-cart) |
+| Product Preview Card | [live](https://grucloud.github.io/bau/frontendmentor/product-preview-card/) | [code](./product-preview-card) |
+| Recipe Page | [live](https://grucloud.github.io/bau/frontendmentor/recipePage/) | [code](./recipePage) |
+| Rest Countries | [live](https://grucloud.github.io/bau/frontendmentor/rest-countries/) | [code](./rest-countries) |
+| Result Summary Component | [live](https://grucloud.github.io/bau/frontendmentor/result-summary-component/) | [code](./result-summary-component) |
+| Social Link Profile | [live](https://grucloud.github.io/bau/frontendmentor/social-link-profile/) | [code](./social-link-profile) |
diff --git a/examples/newsletter-signup-form/.gitignore b/examples/newsletter-signup-form/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/examples/newsletter-signup-form/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/examples/newsletter-signup-form/.npmrc b/examples/newsletter-signup-form/.npmrc
new file mode 100644
index 00000000..6b5f38e8
--- /dev/null
+++ b/examples/newsletter-signup-form/.npmrc
@@ -0,0 +1,2 @@
+save-exact = true
+package-lock = false
diff --git a/examples/newsletter-signup-form/README.md b/examples/newsletter-signup-form/README.md
new file mode 100644
index 00000000..e3cba5e7
--- /dev/null
+++ b/examples/newsletter-signup-form/README.md
@@ -0,0 +1,23 @@
+# Frontend Mentor Newsletter Signup Form
+
+Here is the implementation in [Bau.js](https://github.com/grucloud/bau) of the [Frontend MentorNewsletter Signup Form code challenge](https://www.frontendmentor.io/challenges/newsletter-signup-form-with-success-message-3FC1AZbNrv/hub)
+
+## Workflow
+
+Install the dependencies:
+
+```sh
+npm install
+```
+
+Start a development server:
+
+```sh
+npm run dev
+```
+
+Build a production version:
+
+```sh
+npm run build
+```
diff --git a/examples/newsletter-signup-form/index.html b/examples/newsletter-signup-form/index.html
new file mode 100644
index 00000000..80e2cfad
--- /dev/null
+++ b/examples/newsletter-signup-form/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ Newsletter Signup Form | FrontendMentor
+
+
+
+
+
+
+
diff --git a/examples/newsletter-signup-form/package.json b/examples/newsletter-signup-form/package.json
new file mode 100644
index 00000000..d8e02897
--- /dev/null
+++ b/examples/newsletter-signup-form/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "frontendmentor-newsletter-signup-form",
+ "homepage": "https://grucloud.github.io/bau/frontendmentor/newsletter-signup-form/",
+ "private": true,
+ "version": "0.95.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "deploy": "gh-pages -d ../../dist"
+ },
+ "devDependencies": {
+ "gh-pages": "6.1.1",
+ "typescript": "^5.0.2",
+ "vite": "^5.2.11"
+ },
+ "dependencies": {
+ "@grucloud/bau": "^0.95.0",
+ "@grucloud/bau-css": "^0.95.0",
+ "@grucloud/bau-ui": "^0.95.0"
+ }
+}
diff --git a/examples/newsletter-signup-form/public/assets/images/favicon-32x32.png b/examples/newsletter-signup-form/public/assets/images/favicon-32x32.png
new file mode 100644
index 00000000..1e2df7f0
Binary files /dev/null and b/examples/newsletter-signup-form/public/assets/images/favicon-32x32.png differ
diff --git a/examples/newsletter-signup-form/public/assets/images/icon-list.svg b/examples/newsletter-signup-form/public/assets/images/icon-list.svg
new file mode 100644
index 00000000..8e1ada38
--- /dev/null
+++ b/examples/newsletter-signup-form/public/assets/images/icon-list.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/newsletter-signup-form/public/assets/images/icon-success.svg b/examples/newsletter-signup-form/public/assets/images/icon-success.svg
new file mode 100644
index 00000000..258cab71
--- /dev/null
+++ b/examples/newsletter-signup-form/public/assets/images/icon-success.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/newsletter-signup-form/public/assets/images/illustration-sign-up-desktop.svg b/examples/newsletter-signup-form/public/assets/images/illustration-sign-up-desktop.svg
new file mode 100644
index 00000000..d5d9895b
--- /dev/null
+++ b/examples/newsletter-signup-form/public/assets/images/illustration-sign-up-desktop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/newsletter-signup-form/public/assets/images/illustration-sign-up-mobile.svg b/examples/newsletter-signup-form/public/assets/images/illustration-sign-up-mobile.svg
new file mode 100644
index 00000000..3ad16b9d
--- /dev/null
+++ b/examples/newsletter-signup-form/public/assets/images/illustration-sign-up-mobile.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/newsletter-signup-form/src/main.ts b/examples/newsletter-signup-form/src/main.ts
new file mode 100644
index 00000000..2a23813e
--- /dev/null
+++ b/examples/newsletter-signup-form/src/main.ts
@@ -0,0 +1,18 @@
+import { createContext, type Context } from "@grucloud/bau-ui/context";
+import newsletterSignupForm from "./newsletterSignupForm";
+import "./style.css";
+
+const context = createContext();
+
+const app = (context: Context) => {
+ const { bau } = context;
+ const { main } = bau.tags;
+ const NewsletterSignupForm = newsletterSignupForm(context);
+
+ return function () {
+ return main(NewsletterSignupForm());
+ };
+};
+
+const App = app(context);
+document.getElementById("app")?.replaceChildren(App());
diff --git a/examples/newsletter-signup-form/src/newsletterSignupForm.ts b/examples/newsletter-signup-form/src/newsletterSignupForm.ts
new file mode 100644
index 00000000..56e7faf6
--- /dev/null
+++ b/examples/newsletter-signup-form/src/newsletterSignupForm.ts
@@ -0,0 +1,201 @@
+import { type Context } from "@grucloud/bau-ui/context";
+
+export default function (context: Context) {
+ const { bau, css } = context;
+ const {
+ div,
+ form,
+ h1,
+ p,
+ label,
+ input,
+ button,
+ article,
+ aside,
+ ul,
+ li,
+ img,
+ span,
+ strong,
+ } = bau.tags;
+
+ const doneState = bau.state(false);
+ const emailState = bau.state("");
+
+ const className = css`
+ display: grid;
+ grid-template-columns: 1fr min-content;
+ grid-template-areas: "form image";
+ @media (max-width: 900px) {
+ grid-template-areas: "image" "form";
+ border-radius: 0;
+ }
+ padding: 1rem;
+ margin-inline: 1rem;
+ background-color: var(--White);
+ border-radius: 2rem;
+ > aside {
+ grid-area: image;
+ width: 100%;
+ & img {
+ display: block;
+ margin: auto;
+ @media (max-width: 900px) {
+ background-image: url("./assets/images/illustration-sign-up-mobile.svg");
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 375px;
+ height: 284px;
+ }
+ }
+ }
+ > form {
+ grid-area: form;
+ margin: auto;
+ padding-inline: 2rem;
+ padding-block: 1rem;
+ max-width: 500px;
+ display: grid;
+ gap: 1rem;
+ > ul {
+ > li {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+ padding-block: 0.3rem;
+ position: relative;
+ &::before {
+ height: 21px;
+ width: 21px;
+ background-image: url("./assets/images/icon-list.svg");
+ content: "";
+ }
+ }
+ }
+ & h1 {
+ font-size: 2.5rem;
+ text-align: center;
+ }
+ > label {
+ display: grid;
+ font-size: 0.8rem;
+ font-weight: bold;
+ gap: 0.5rem;
+ > div {
+ display: flex;
+ justify-content: space-between;
+ .error {
+ color: var(--color-danger);
+ display: none;
+ }
+ }
+ &:has(> input:user-invalid) {
+ .error {
+ display: block;
+ color: var(--color-danger);
+ }
+ }
+ > input {
+ height: 56px;
+ padding-inline: 1rem;
+ border-radius: 0.5rem;
+ color: inherit;
+ border: 1px solid var(--grey);
+ &:focus {
+ outline: 1px auto var(--color-primary);
+ }
+ &:user-invalid {
+ outline: 1px auto var(--color-danger);
+ background-color: var(--color-danger-lightest);
+ color: var(--color-danger);
+ }
+ &:user-valid {
+ outline: 1px auto var(--color-success);
+ }
+ }
+ }
+ }
+ `;
+
+ const onsubmit = (event: any) => {
+ event.preventDefault();
+ const { email } = Object.fromEntries(new FormData(event.target));
+ emailState.val = String(email);
+ doneState.val = true;
+ };
+
+ const Thankyou = ({}) =>
+ form(
+ {
+ onsubmit: (event: any) => {
+ event.preventDefault();
+ },
+ class: css`
+ background-color: var(--White);
+ max-width: 400px;
+ padding: 3rem;
+ margin-inline: 1rem;
+ border-radius: 0.5rem;
+ display: grid;
+ gap: 2rem;
+ & img {
+ }
+ `,
+ },
+ img({ src: "./assets/images/icon-success.svg", width: 64, height: 64 }),
+ h1("Thanks for subscribing!"),
+ p(
+ "A confirmation email has been sent to ",
+ strong(emailState.val),
+ ". Please open it and click the button inside to confirm your subscription."
+ ),
+ button(
+ {
+ type: "submit",
+ onclick: () => {
+ doneState.val = false;
+ },
+ },
+ "Dismiss message"
+ )
+ );
+
+ const Newsletter = () =>
+ article(
+ { class: className },
+ form(
+ { onsubmit },
+ h1("Stay updated!"),
+ p("Join 60,000+ product managers receiving monthly updates on:"),
+ ul(
+ li("Product discovery and building what matters"),
+ li("Measuring to ensure updates are a success"),
+ li("And much more!")
+ ),
+ label(
+ div(
+ "Email address",
+ span({ class: "error" }, "Valid email required")
+ ),
+ input({
+ name: "email",
+ type: "email",
+ placeholder: "email@company.com",
+ required: true,
+ })
+ ),
+ p(button({ type: "submit" }, "Subscribe to monthly newsletter"))
+ ),
+ aside(
+ img({
+ width: 400,
+ height: 593,
+ src: "./assets/images/illustration-sign-up-desktop.svg",
+ })
+ )
+ );
+
+ return () => {
+ return div(() => (doneState.val ? Thankyou({}) : Newsletter()));
+ };
+}
diff --git a/examples/newsletter-signup-form/src/style.css b/examples/newsletter-signup-form/src/style.css
new file mode 100644
index 00000000..535260b4
--- /dev/null
+++ b/examples/newsletter-signup-form/src/style.css
@@ -0,0 +1,54 @@
+@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --tomato: hsl(4, 100%, 67%);
+ --dark-slate-grey: hsl(234, 29%, 20%);
+ --charcoal-grey: hsl(235, 18%, 26%);
+ --grey: hsl(231, 7%, 60%);
+ --White: hsl(0, 0%, 100%);
+ --color-primary-h: 4;
+ --color-primary-base-s: 100%;
+ --color-primary-l: 67%;
+
+ --color-neutral-h: 245;
+ --color-neutral-base-s: 18%;
+ --color-neutral-l: 26%;
+
+ --color-danger-h: 358;
+ --color-danger-base-s: 79%;
+ --color-danger-l: 66%;
+
+ --color-danger-lightest: hsl(358, 79%, calc(66% * 1.4));
+
+ --font-color-primary: white;
+ --background-color: white;
+}
+body {
+ background-color: hsl(234, 29%, 20%);
+ font: 400 16px/1.6 "Roboto", sans-serif;
+ min-height: 100vh;
+ display: grid;
+ place-content: center;
+ color: var(--dark-slate-grey);
+}
+
+button[type="submit"] {
+ height: 56px;
+ width: 100%;
+ border-radius: 0.5rem;
+ background-color: var(--dark-slate-grey);
+ color: var(--font-color-primary);
+ font-weight: 700;
+ font-size: 1rem;
+ cursor: pointer;
+ border: none;
+ transition: all 0.3s;
+ &:hover {
+ background-image: linear-gradient(45deg, #ff527a, #fd6937);
+ }
+}
diff --git a/examples/newsletter-signup-form/src/vite-env.d.ts b/examples/newsletter-signup-form/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/examples/newsletter-signup-form/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/newsletter-signup-form/tsconfig.json b/examples/newsletter-signup-form/tsconfig.json
new file mode 100644
index 00000000..75abdef2
--- /dev/null
+++ b/examples/newsletter-signup-form/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/newsletter-signup-form/vite.config.js b/examples/newsletter-signup-form/vite.config.js
new file mode 100644
index 00000000..21c887cd
--- /dev/null
+++ b/examples/newsletter-signup-form/vite.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from "vite";
+
+export default defineConfig(({ command, mode, ssrBuild }) => {
+ return {
+ base: "/bau/frontendmentor/newsletter-signup-form/",
+ build: { outDir: "../../dist/frontendmentor/newsletter-signup-form" },
+ server: {
+ open: true,
+ },
+ };
+});