diff --git a/packages/pluggableWidgets/skiplink-web/.gitignore b/packages/pluggableWidgets/skiplink-web/.gitignore
new file mode 100644
index 0000000000..a1bd0102fd
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/.gitignore
@@ -0,0 +1,14 @@
+/tests/TestProjects/**/.classpath
+/tests/TestProjects/**/.project
+/tests/TestProjects/**/javascriptsource
+/tests/TestProjects/**/javasource
+/tests/TestProjects/**/resources
+/tests/TestProjects/**/userlib
+
+/tests/TestProjects/Mendix8/theme/styles/native
+/tests/TestProjects/Mendix8/theme/styles/web/sass
+/tests/TestProjects/Mendix8/theme/*.*
+!/tests/TestProjects/Mendix8/theme/components.json
+!/tests/TestProjects/Mendix8/theme/favicon.ico
+!/tests/TestProjects/Mendix8/theme/LICENSE
+!/tests/TestProjects/Mendix8/theme/settings.json
diff --git a/packages/pluggableWidgets/skiplink-web/.prettierrc.js b/packages/pluggableWidgets/skiplink-web/.prettierrc.js
new file mode 100644
index 0000000000..0892704ab0
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/.prettierrc.js
@@ -0,0 +1 @@
+module.exports = require("@mendix/prettier-config-web-widgets");
diff --git a/packages/pluggableWidgets/skiplink-web/README.md b/packages/pluggableWidgets/skiplink-web/README.md
new file mode 100644
index 0000000000..02b3c70903
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/README.md
@@ -0,0 +1,27 @@
+# SkipLink Web Widget
+
+A simple accessibility widget that adds a skip link to the top of the page. The link is only visible when focused and allows users to jump directly to the main content.
+
+## Usage
+
+1. Place the `` component at the very top of your page or layout.
+2. Ensure your main content container has `id="main-content"`.
+
+    ```jsx
+    
+    Main content here
+    ```
+
+## Accessibility
+
+- The skip link is visually hidden except when focused, making it accessible for keyboard and screen reader users.
+
+## End-to-End Testing
+
+E2E tests are located in the `e2e/` folder and use Playwright. Run them with:
+
+```
+npm install
+npx playwright install
+npm test
+```
diff --git a/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts
new file mode 100644
index 0000000000..80c13e8155
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.ts
@@ -0,0 +1,23 @@
+import { expect, test } from "@playwright/test";
+
+// Assumes the test project renders  and a  element
+
+test.describe("SkipLink", () => {
+    test("should be hidden by default and visible on focus, and should skip to main content", async ({ page }) => {
+        await page.goto("/");
+        const skipLink = page.locator(".skip-link");
+        // Should be hidden by default
+        await expect(skipLink).toHaveCSS("transform", "matrix(1, 0, 0, 1, 0, -120)");
+        // Tab to focus the skip link
+        await page.keyboard.press("Tab");
+        await expect(skipLink).toBeVisible();
+        // Check if skipLink is the active element
+        const isFocused = await skipLink.evaluate(node => node === document.activeElement);
+        expect(isFocused).toBe(true);
+        // Press Enter to activate the link
+        await page.keyboard.press("Enter");
+        // The main content should be focused or scrolled into view
+        const main = page.locator("#main-content");
+        await expect(main).toBeVisible();
+    });
+});
diff --git a/packages/pluggableWidgets/skiplink-web/e2e/package.json b/packages/pluggableWidgets/skiplink-web/e2e/package.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/pluggableWidgets/skiplink-web/eslint.config.mjs b/packages/pluggableWidgets/skiplink-web/eslint.config.mjs
new file mode 100644
index 0000000000..ed68ae9e78
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/eslint.config.mjs
@@ -0,0 +1,3 @@
+import config from "@mendix/eslint-config-web-widgets/widget-ts.mjs";
+
+export default config;
diff --git a/packages/pluggableWidgets/skiplink-web/jest.config.js b/packages/pluggableWidgets/skiplink-web/jest.config.js
new file mode 100644
index 0000000000..88999d5568
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/jest.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+    ...require("@mendix/pluggable-widgets-tools/test-config/jest.enzyme-free.config.js")
+};
diff --git a/packages/pluggableWidgets/skiplink-web/package.json b/packages/pluggableWidgets/skiplink-web/package.json
new file mode 100644
index 0000000000..dd660ba034
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/package.json
@@ -0,0 +1,55 @@
+{
+    "name": "@mendix/skiplink-web",
+    "widgetName": "SkipLink",
+    "version": "1.0.0",
+    "description": "Adds a skip link to the top of the page for accessibility.",
+    "copyright": "© Mendix Technology BV 2025. All rights reserved.",
+    "license": "Apache-2.0",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/mendix/web-widgets.git"
+    },
+    "config": {},
+    "mxpackage": {
+        "name": "SkipLink",
+        "type": "widget",
+        "mpkName": "com.mendix.widget.web.SkipLink.mpk"
+    },
+    "packagePath": "com.mendix.widget.web",
+    "marketplace": {
+        "minimumMXVersion": "11.1.0",
+        "appNumber": 119999,
+        "appName": "SkipLink",
+        "reactReady": true
+    },
+    "scripts": {
+        "build": "pluggable-widgets-tools build:web",
+        "create-gh-release": "rui-create-gh-release",
+        "create-translation": "rui-create-translation",
+        "dev": "pluggable-widgets-tools start:web",
+        "e2e": "run-e2e ci",
+        "e2edev": "run-e2e dev --with-preps",
+        "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .",
+        "lint": "eslint src/ package.json",
+        "publish-marketplace": "rui-publish-marketplace",
+        "release": "pluggable-widgets-tools release:web",
+        "start": "pluggable-widgets-tools start:server",
+        "test": "jest --projects jest.config.js",
+        "update-changelog": "rui-update-changelog-widget",
+        "verify": "rui-verify-package-format"
+    },
+    "dependencies": {
+        "@floating-ui/react": "^0.26.27",
+        "@mendix/widget-plugin-component-kit": "workspace:*",
+        "classnames": "^2.5.1"
+    },
+    "devDependencies": {
+        "@mendix/automation-utils": "workspace:*",
+        "@mendix/eslint-config-web-widgets": "workspace:*",
+        "@mendix/pluggable-widgets-tools": "*",
+        "@mendix/prettier-config-web-widgets": "workspace:*",
+        "@mendix/run-e2e": "workspace:*",
+        "@mendix/widget-plugin-hooks": "workspace:*",
+        "@mendix/widget-plugin-platform": "workspace:*"
+    }
+}
diff --git a/packages/pluggableWidgets/skiplink-web/playwright.config.cjs b/packages/pluggableWidgets/skiplink-web/playwright.config.cjs
new file mode 100644
index 0000000000..29045fc372
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/playwright.config.cjs
@@ -0,0 +1 @@
+module.exports = require("@mendix/run-e2e/playwright.config.cjs");
diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts
new file mode 100644
index 0000000000..6fa0fe3dbb
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts
@@ -0,0 +1,80 @@
+import { Problem, Properties } from "@mendix/pluggable-widgets-tools";
+import {
+    StructurePreviewProps,
+    RowLayoutProps,
+    ContainerProps,
+    TextProps,
+    structurePreviewPalette
+} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
+
+export function getProperties(defaultValues: Properties): Properties {
+    // No conditional properties for skiplink, but function provided for consistency
+    return defaultValues;
+}
+
+export function check(values: any): Problem[] {
+    const errors: Problem[] = [];
+    if (!values.linkText) {
+        errors.push({
+            property: "linkText",
+            message: "Link text is required"
+        });
+    }
+    if (!values.mainContentId) {
+        errors.push({
+            property: "mainContentId",
+            message: "Main content ID is required"
+        });
+    }
+    return errors;
+}
+
+export function getPreview(values: any, isDarkMode: boolean): StructurePreviewProps | null {
+    const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"];
+    const titleHeader: RowLayoutProps = {
+        type: "RowLayout",
+        columnSize: "grow",
+        backgroundColor: palette.background.topbarStandard,
+        borders: true,
+        borderWidth: 1,
+        children: [
+            {
+                type: "Container",
+                padding: 4,
+                children: [
+                    {
+                        type: "Text",
+                        content: "SkipLink",
+                        fontColor: palette.text.secondary
+                    } as TextProps
+                ]
+            }
+        ]
+    };
+    const linkContent: RowLayoutProps = {
+        type: "RowLayout",
+        columnSize: "grow",
+        borders: true,
+        padding: 0,
+        children: [
+            {
+                type: "Container",
+                padding: 6,
+                children: [
+                    {
+                        type: "Text",
+                        content: values.linkText || "Skip to main content",
+                        fontSize: 14,
+                        fontColor: palette.text.primary,
+                        bold: true
+                    } as TextProps
+                ]
+            }
+        ]
+    };
+    return {
+        type: "Container",
+        borders: true,
+        children: [titleHeader, linkContent]
+    } as ContainerProps;
+}
diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx
new file mode 100644
index 0000000000..543e043658
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx
@@ -0,0 +1,34 @@
+import { createElement, ReactElement } from "react";
+import { SkipLinkPreviewProps } from "../typings/SkipLinkProps";
+
+export const preview = (props: SkipLinkPreviewProps): ReactElement => {
+    if (props.renderMode === "xray") {
+        return (
+            
+        );
+    }
+    return ;
+};
+
+export function getPreviewCss(): string {
+    return require("./ui/SkipLink.scss");
+}
diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx
new file mode 100644
index 0000000000..2c748c0263
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.tsx
@@ -0,0 +1,66 @@
+import "./ui/SkipLink.scss";
+import { useEffect } from "react";
+
+export interface SkipLinkProps {
+    /**
+     * The text displayed for the skip link.
+     */
+    linkText: string;
+    /**
+     * The id of the main content element to jump to.
+     */
+    mainContentId: string;
+}
+
+/**
+ * Inserts a skip link as the first child of the element with ID 'root'.
+ * When activated, focus is programmatically set to the main content.
+ */
+export function SkipLink({ linkText, mainContentId }: SkipLinkProps): null {
+    useEffect(() => {
+        // Create the skip link element
+        const link = document.createElement("a");
+        link.href = `#${mainContentId}`;
+        link.className = "skip-link";
+        link.textContent = linkText;
+        link.tabIndex = 0;
+
+        // Handler to move focus to the main content
+        function handleClick(event: MouseEvent) {
+            event.preventDefault();
+            const main = document.getElementById(mainContentId);
+            if (main) {
+                // Store previous tabindex
+                const prevTabIndex = main.getAttribute("tabindex");
+                // Ensure main is focusable
+                if (!main.hasAttribute("tabindex")) {
+                    main.setAttribute("tabindex", "-1");
+                }
+                main.focus();
+                // Clean up tabindex if it was not present before
+                if (prevTabIndex === null) {
+                    main.addEventListener("blur", () => main.removeAttribute("tabindex"), { once: true });
+                }
+            }
+        }
+
+        link.addEventListener("click", handleClick);
+
+        // Insert as the first child of the element with ID 'root'
+        const root = document.getElementById("root");
+        if (root) {
+            root.insertBefore(link, root.firstChild);
+        }
+
+        // Cleanup on unmount
+        return () => {
+            link.removeEventListener("click", handleClick);
+            if (link.parentNode) {
+                link.parentNode.removeChild(link);
+            }
+        };
+    }, [linkText, mainContentId]);
+
+    // This component does not render anything in the React tree
+    return null;
+}
diff --git a/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml
new file mode 100644
index 0000000000..9769fe7d01
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/src/SkipLink.xml
@@ -0,0 +1,20 @@
+
+
+    SkipLink
+    A skip link for accessibility, allowing users to jump directly to the main content.
+    Accessibility
+    Accessibility
+    https://docs.mendix.com/appstore/widgets/skiplink
+    
+        
+            
+                Link text
+                The text displayed for the skip link.
+            
+            
+                Main content ID
+                The id of the main content element to jump to.
+            
+        
+    
+
diff --git a/packages/pluggableWidgets/skiplink-web/src/package.xml b/packages/pluggableWidgets/skiplink-web/src/package.xml
new file mode 100644
index 0000000000..811a87e4ab
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/src/package.xml
@@ -0,0 +1,11 @@
+
+
+    
+        
+            
+        
+        
+            
+        
+    
+
diff --git a/packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.scss b/packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.scss
new file mode 100644
index 0000000000..5a613ceb39
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/src/ui/SkipLink.scss
@@ -0,0 +1,20 @@
+.skip-link {
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: #fff;
+    color: #0078d4;
+    padding: 8px 16px;
+    z-index: 1000;
+    transform: translateY(-120%);
+    transition: transform 0.2s;
+    text-decoration: none;
+    border: 2px solid #0078d4;
+    border-radius: 4px;
+    font-weight: bold;
+}
+
+.skip-link:focus {
+    transform: translateY(0);
+    outline: none;
+}
diff --git a/packages/pluggableWidgets/skiplink-web/tsconfig.json b/packages/pluggableWidgets/skiplink-web/tsconfig.json
new file mode 100644
index 0000000000..a2a5b87e60
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/tsconfig.json
@@ -0,0 +1,31 @@
+{
+    "include": ["./src", "./typings"],
+    "compilerOptions": {
+        "baseUrl": "./",
+        "noEmitOnError": true,
+        "sourceMap": true,
+        "module": "esnext",
+        "target": "es6",
+        "lib": ["esnext", "dom"],
+        "types": ["jest", "node"],
+        "moduleResolution": "node",
+        "declaration": false,
+        "noLib": false,
+        "forceConsistentCasingInFileNames": true,
+        "noFallthroughCasesInSwitch": true,
+        "strict": true,
+        "strictFunctionTypes": false,
+        "skipLibCheck": true,
+        "noUnusedLocals": true,
+        "noUnusedParameters": true,
+        "jsx": "react",
+        "jsxFactory": "createElement",
+        "allowSyntheticDefaultImports": true,
+        "esModuleInterop": true,
+        "useUnknownInCatchVariables": false,
+        "exactOptionalPropertyTypes": false,
+        "paths": {
+            "react-hot-loader/root": ["./hot-typescript.ts"]
+        }
+    }
+}
diff --git a/packages/pluggableWidgets/skiplink-web/typings/SkipLinkProps.d.ts b/packages/pluggableWidgets/skiplink-web/typings/SkipLinkProps.d.ts
new file mode 100644
index 0000000000..dd4d4c8a82
--- /dev/null
+++ b/packages/pluggableWidgets/skiplink-web/typings/SkipLinkProps.d.ts
@@ -0,0 +1,30 @@
+/**
+ * This file was generated from SkipLink.xml
+ * WARNING: All changes made to this file will be overwritten
+ * @author Mendix Widgets Framework Team
+ */
+import { CSSProperties } from "react";
+
+export interface SkipLinkContainerProps {
+    name: string;
+    class: string;
+    style?: CSSProperties;
+    tabIndex?: number;
+    linkText: string;
+    mainContentId: string;
+}
+
+export interface SkipLinkPreviewProps {
+    /**
+     * @deprecated Deprecated since version 9.18.0. Please use class property instead.
+     */
+    className: string;
+    class: string;
+    style: string;
+    styleObject?: CSSProperties;
+    readOnly: boolean;
+    renderMode: "design" | "xray" | "structure";
+    translate: (text: string) => string;
+    linkText: string;
+    mainContentId: string;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 40d480cc17..4f024987f7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2138,6 +2138,40 @@ importers:
         specifier: ^7.0.3
         version: 7.0.3
 
+  packages/pluggableWidgets/skiplink-web:
+    dependencies:
+      '@floating-ui/react':
+        specifier: ^0.26.27
+        version: 0.26.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@mendix/widget-plugin-component-kit':
+        specifier: workspace:*
+        version: link:../../shared/widget-plugin-component-kit
+      classnames:
+        specifier: ^2.5.1
+        version: 2.5.1
+    devDependencies:
+      '@mendix/automation-utils':
+        specifier: workspace:*
+        version: link:../../../automation/utils
+      '@mendix/eslint-config-web-widgets':
+        specifier: workspace:*
+        version: link:../../shared/eslint-config-web-widgets
+      '@mendix/pluggable-widgets-tools':
+        specifier: 10.21.2
+        version: 10.21.2(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.1)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.27.4)(@babel/preset-env@7.26.9(@babel/core@7.27.4))(@types/react@18.2.36)(react@18.2.0))(react@18.2.0)(tslib@2.8.1)
+      '@mendix/prettier-config-web-widgets':
+        specifier: workspace:*
+        version: link:../../shared/prettier-config-web-widgets
+      '@mendix/run-e2e':
+        specifier: workspace:*
+        version: link:../../../automation/run-e2e
+      '@mendix/widget-plugin-hooks':
+        specifier: workspace:*
+        version: link:../../shared/widget-plugin-hooks
+      '@mendix/widget-plugin-platform':
+        specifier: workspace:*
+        version: link:../../shared/widget-plugin-platform
+
   packages/pluggableWidgets/slider-web:
     dependencies:
       '@mendix/widget-plugin-component-kit':
@@ -17602,7 +17636,7 @@ snapshots:
     dependencies:
       array-union: 2.1.0
       dir-glob: 3.0.1
-      fast-glob: 3.3.2
+      fast-glob: 3.3.3
       ignore: 5.3.2
       merge2: 1.4.1
       slash: 3.0.0