diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000000..6313b56c578
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000..b1f04ad75d3
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,14 @@
+# Lines starting with '#' are comments.
+# Each line is a file pattern followed by one or more owners.
+
+# More details are here: https://help.github.com/articles/about-codeowners/
+
+# The '*' pattern is global owners.
+
+# Order is important. The last matching pattern has the most precedence.
+# The folders are ordered as follows:
+
+# In each subsection folders are ordered first by depth, then alphabetically.
+# This should make it easy to add new rules without breaking existing ones.
+
+* @ionic-team/framework
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
deleted file mode 100644
index b5cd6b64a79..00000000000
--- a/.github/CONTRIBUTING.md
+++ /dev/null
@@ -1,140 +0,0 @@
-# Contributing
-
-Thanks for your interest in contributing to the Ionic Framework! :tada:
-
-
-## Contributing Etiquette
-
-Please see our [Contributor Code of Conduct](https://github.com/driftyco/ionic/blob/master/CODE_OF_CONDUCT.md) for information on our rules of conduct.
-
-
-## Creating an Issue
-
-If you have a question about using the framework, please ask on the [Ionic Forum](http://forum.ionicframework.com/) or in the [Ionic Worldwide Slack](http://ionicworldwide.herokuapp.com/) group.
-
-If you think you have found a bug, or have a new feature idea, please start by making sure it hasn't already been [reported](https://github.com/driftyco/ionic/issues?utf8=%E2%9C%93&q=is%3Aissue). You can search through existing issues to see if there is a similar one reported. Include closed issues as it may have been closed with a solution.
-
-Next, [create a new issue](https://github.com/driftyco/ionic/issues/new) that thoroughly explains the problem. Please fill out the populated issue form before submitting the issue.
-
-
-## Creating a Pull Request
-
-We appreciate you taking the time to contribute! Before submitting a pull request, we ask that you please [create an issue](#creating-an-issue) that explains the bug or feature request and let us know that you plan on creating a pull request for it. If an issue already exists, please comment on that issue letting us know you would like to submit a pull request for it. This helps us to keep track of the pull request and make sure there isn't duplicated effort.
-
-Looking for an issue to fix? Make sure to look through our issues with the [help wanted](https://github.com/driftyco/ionic/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) label!
-
-### Setup
-
-1. Fork the repo.
-2. Clone your fork.
-3. Make a branch for your change.
-4. Run `npm install` (make sure you have [node](https://nodejs.org/en/) and [npm](http://blog.npmjs.org/post/85484771375/how-to-install-npm) installed first)
-
-
-### Modifying Components
-
-1. Make any changes to the component.
-2. Modify the e2e test in the `test/` directory under the component directory, if possible. If the test does not exist and it is possible to show the change, please create a new test in a directory called `basic/`.
-
-
-#### TypeScript Changes
-
-1. If there is a `*.spec.ts` file located in the `test/` folder, update it to include a karma test for your change, if needed. If this file doesn't exist, please notify us.
-2. Run `gulp test` to make sure all tests are working, regardless if a test was added.
-3. Run `gulp lint.ts` and fix any linter errors.
-
-
-#### Sass Changes
-
-1. If the css property is something that the user may want to override and it won't break the component layout, it should be given a Sass variable. See our [doc on naming Sass variables](https://docs.google.com/document/d/1OyOyrRE5lpB_9mdkF0HWVQLV97fHma450N8XqE4mjZQ/edit?usp=sharing).
-2. After any changes to the Sass files run the [Sass Linter](https://github.com/brigade/scss-lint):
- - Requires [Ruby](https://www.ruby-lang.org/en/documentation/installation/). **Skip this step entirely if you are unable to install Ruby.**
- - Install the linter: `gem install scss_lint`
- - Make sure to run the linter at the root of the repository.
- - Run `gulp lint.sass` and fix any linter errors.
-
-
-#### Viewing Changes
-
-1. Run the gulp e2e task to build all tests: `gulp e2e`
-2. Run the gulp e2e.watch task to watch your specific test (replace `button` with the component you are modifying and `basic` with the test folder): `gulp e2e.watch --f=button/basic`
-3. A browser should open at `http://localhost:8080/dist/e2e`. From here, navigate to the component you are changing.
-4. If your changes look good, you're ready to [commit](#committing)!
-
-
-#### Adding Documentation
-
-1. To add or modify API Documentation for a component, it should be added/changed in the component's TypeScript (`*.ts`) file, prior to the Class definition. For example, `Badge` looks similar to this:
-
- ```
- /**
- * @name Badge
- * @module ionic
- * @description
- * Badges are simple components in Ionic containing numbers or text.
- *
- * @see {@link /docs/v2/components/#badges Badges Component Docs}
- * @demo /docs/v2/demos/badge/
- **/
- ```
-
- where `@name` is the Class name, `@description` is the description displayed on the documentation page, `@see` links to any related pages, and `@demo` links to the API demo located in the `demos` folder.
-2. In order to run API documentation locally, you will need to clone the `ionic-site` repo as a sibling to the `ionic` repo and then run it: https://github.com/driftyco/ionic-site#local-build
-3. Then, run `gulp docs` in the `ionic` repo every time you make a change and the site will update.
-4. If the change affects the component documentation, create an issue on the `ionic-site` repo: https://github.com/driftyco/ionic-site/issues
-
-
-#### Adding Demos
-
-1. Create or modify the demo in the `demos/` folder.
-2. If it is new, link to the demo in the component's TypeScript (`*.ts`) file (under `src/components`) by adding a link to it in the documentation using `@demo`, for example:
-
- ```
- /**
- * @name Badge
- *
- * ...
- *
- * @demo /docs/v2/demos/src/badge/
- **/
- ```
-3. Run `gulp watch.demos` to watch for changes to the demo
-4. Navigate to `http://localhost:8000/dist/demos/` and then to your component's demo to view it.
-5. If the change affects the component demos, create an issue on the `ionic-site` repo: https://github.com/driftyco/ionic-site/issues
-
-
-## Commit Message Format
-
-We have very precise rules over how our git commit messages should be formatted. This leads to readable messages that are easy to follow when looking through the project history. We also use the git commit messages to generate our [changelog](https://github.com/driftyco/ionic/blob/master/CHANGELOG.md). (Ok you got us, it's basically Angular's commit message format).
-
-`type(scope): subject`
-
-#### Type
-Must be one of the following:
-
-* **feat**: A new feature
-* **fix**: A bug fix
-* **docs**: Documentation only changes
-* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
-* **refactor**: A code change that neither fixes a bug nor adds a feature
-* **perf**: A code change that improves performance
-* **test**: Adding missing tests
-* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation
-
-#### Scope
-The scope can be anything specifying place of the commit change. For example `action-sheet`, `button`, `menu`, `nav`, etc. If you make multiple commits for the same component, please keep the naming of this component consistent. For example, if you make a change to navigation and the first commit is `fix(nav)`, you should continue to use `nav` for any more commits related to navigation.
-
-#### Subject
-The subject contains succinct description of the change:
-
-* use the imperative, present tense: "change" not "changed" nor "changes"
-* do not capitalize first letter
-* do not place a period `.` at the end
-* entire length of the commit message must not go over 50 characters
-* describe what the commit does, not what issue it relates to or fixes
-* **be brief, yet descriptive** - we should have a good understanding of what the commit does by reading the subject
-
-
-## License
-
-By contributing your code to the driftyco/ionic GitHub Repository, you agree to license your contribution under the MIT license.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 183b268d333..2fa1c3e3549 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,11 +1,23 @@
-**Ionic version:** (check one with "x")
-[ ] **1.x**
-[ ] **2.x**
+
-**I'm submitting a ...** (check one with "x")
+
+
+
+
+
+
+**Ionic version:**
+
+
+[x] **4.x**
+[ ] **5.x**
+
+**I'm submitting a ...**
+
[ ] bug report
[ ] feature request
-[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/
+
+
**Current behavior:**
@@ -14,26 +26,30 @@
**Steps to reproduce:**
-
-For Ionic 1 issues - http://plnkr.co/edit/Xo1QyAUx35ny1Xf9ODHx?p=preview
+**Related code:**
-For Ionic 2 issues - http://plnkr.co/edit/GJte2b?p=preview
--->
+
```
-insert any relevant code here
+insert short code snippets here
```
**Other information:**
-**Ionic info:** (run `ionic info` from a terminal/cmd prompt and paste output below):
+**Ionic info:**
+
```
insert the output from ionic info here
```
-
-
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000000..98905a6c843
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,86 @@
+name: π Bug Report
+description: Create a report to help us improve Ionic Framework
+title: 'bug: '
+
+body:
+ - type: checkboxes
+ id: prerequisites
+ attributes:
+ label: Prerequisites
+ description: Please ensure you have completed all of the following.
+ options:
+ - label: I have read the [Contributing Guidelines](https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#creating-an-issue).
+ required: true
+ - label: I agree to follow the [Code of Conduct](https://ionicframework.com/code-of-conduct).
+ required: true
+ - label: I have searched for [existing issues](https://github.com/ionic-team/ionic-framework/issues) that already report this problem, without success.
+ required: true
+
+ - type: dropdown
+ id: affected-versions
+ attributes:
+ label: Ionic Framework Version
+ description: Which version(s) of Ionic Framework does this issue impact? [Ionic Framework 1.x to 7.x are no longer supported](https://ionicframework.com/docs/reference/support#framework-maintenance-and-support-status).
+ options:
+ - v8.x
+ - v9.x
+ - Nightly
+ multiple: true
+ validations:
+ required: true
+
+ - type: textarea
+ id: current-behavior
+ attributes:
+ label: Current Behavior
+ description: A clear description of what the bug is and how it manifests.
+ validations:
+ required: true
+
+ - type: textarea
+ id: expected-behavior
+ attributes:
+ label: Expected Behavior
+ description: A clear description of what you expected to happen.
+ validations:
+ required: true
+
+ - type: textarea
+ id: steps-to-reproduce
+ attributes:
+ label: Steps to Reproduce
+ description: Explain the steps required to reproduce this issue.
+ placeholder: |
+ 1. Go to '...'
+ 2. Click on '...'
+ 3. Observe: '...'
+ validations:
+ required: true
+
+ - type: input
+ id: reproduction-url
+ attributes:
+ label: Code Reproduction URL
+ description: |
+ Reproduce this issue in a blank [Ionic Framework starter application](https://ionicframework.com/start#basics) or a Stackblitz example.
+
+ You can use the Stackblitz button available on any of the [component playgrounds](https://ionicframework.com/docs/components) to open an editable example. Remember to save your changes to obtain a link to copy.
+
+ Reproductions cases must be minimal and focused around the specific problem you are experiencing. This is the best way to ensure this issue is triaged quickly. Issues without a code reproduction may be closed if the Ionic Team cannot reproduce the issue you are reporting.
+ placeholder: https://github.com/...
+ validations:
+ required: true
+
+ - type: textarea
+ id: ionic-info
+ attributes:
+ label: Ionic Info
+ description: Please run `ionic info` from within your Ionic Framework project directory and paste the output below.
+ validations:
+ required: true
+
+ - type: textarea
+ id: additional-information
+ attributes:
+ label: Additional Information
+ description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000000..25a8dbb606f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,10 @@
+contact_links:
+ - name: π Documentation
+ url: https://github.com/ionic-team/ionic-docs/issues/new/choose
+ about: This issue tracker is not for documentation issues. Please file documentation issues on the Ionic Docs repo.
+ - name: π» CLI
+ url: https://github.com/ionic-team/ionic-cli/issues/new/choose
+ about: This issue tracker is not for CLI issues. Please file CLI issues on the Ionic CLI repo.
+ - name: π€ Support Question
+ url: https://forum.ionicframework.com/
+ about: This issue tracker is not for support questions. Please post your question on the Ionic Forums.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 00000000000..788d41e3a2e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,57 @@
+name: π‘ Feature Request
+description: Suggest an idea for Ionic Framework
+title: 'feat: '
+body:
+
+ - type: checkboxes
+ id: prerequisites
+ attributes:
+ label: Prerequisites
+ description: Please ensure you have completed all of the following.
+ options:
+ - label: I have read the [Contributing Guidelines](https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#creating-an-issue).
+ required: true
+ - label: I agree to follow the [Code of Conduct](https://ionicframework.com/code-of-conduct).
+ required: true
+ - label: I have searched for [existing issues](https://github.com/ionic-team/ionic-framework/issues) that already include this feature request, without success.
+ required: true
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Describe the Feature Request
+ description: A clear and concise description of what the feature does.
+ validations:
+ required: true
+
+ - type: textarea
+ id: use-case
+ attributes:
+ label: Describe the Use Case
+ description: A clear and concise use case for what problem this feature would solve.
+ validations:
+ required: true
+
+ - type: textarea
+ id: proposed-solution
+ attributes:
+ label: Describe Preferred Solution
+ description: A clear and concise description of what you how you want this feature to be added to Ionic Framework.
+
+ - type: textarea
+ id: alternatives-considered
+ attributes:
+ label: Describe Alternatives
+ description: A clear and concise description of any alternative solutions or features you have considered.
+
+ - type: textarea
+ id: related-code
+ attributes:
+ label: Related Code
+ description: If you are able to illustrate the feature request with an example, please provide a sample Ionic Framework application. Try out our [Getting Started Wizard](https://ionicframework.com/start#basics) to quickly spin up an Ionic Framework starter app.
+
+ - type: textarea
+ id: additional-information
+ attributes:
+ label: Additional Information
+ description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to implement, Stack Overflow links, forum links, etc.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 9df38c036f8..29910b7b2b2 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,12 +1,34 @@
-#### Short description of what this resolves:
+Issue number: resolves #
+---------
-#### Changes proposed in this pull request:
+
+
+
+
+## What is the current behavior?
+
+
+## What is the new behavior?
+
-
-
-
-**Ionic Version**: 1.x / 2.x
+## Does this introduce a breaking change?
+
+- [ ] Yes
+- [ ] No
+
+
+
+
+## Other information
-**Fixes**: #
+
diff --git a/.github/actions/publish-npm/action.yml b/.github/actions/publish-npm/action.yml
new file mode 100644
index 00000000000..6913b493ef1
--- /dev/null
+++ b/.github/actions/publish-npm/action.yml
@@ -0,0 +1,60 @@
+name: 'Release'
+description: 'Releases a package'
+inputs:
+ scope:
+ description: 'The package to release. Must match a package specified in lerna.json.'
+ version:
+ description: 'The type of version to release.'
+ tag:
+ description: 'The tag to publish to on NPM.'
+ preid:
+ description: "Prerelease identifier such as 'alpha', 'beta', 'rc', or 'next'. Leave blank to skip prerelease tagging."
+ working-directory:
+ description: 'The directory of the package.'
+ folder:
+ default: './'
+ description: 'A folder containing a package.json file.'
+ node-version:
+ description: 'Node.js version to use when publishing.'
+ required: false
+ default: '24.x'
+runs:
+ using: 'composite'
+ steps:
+ - name: π’ Configure Node for Publish
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: ${{ inputs.node-version }}
+ registry-url: 'https://registry.npmjs.org'
+ scope: '@ionic'
+ # Provenance requires npm 9.5.0+
+ - name: π¦ Install latest npm
+ run: npm install -g npm@latest
+ shell: bash
+ # This ensures the local version of Lerna is installed
+ # and that we do not use the global Lerna version
+ - name: πΈοΈ Install root dependencies
+ run: npm ci
+ shell: bash
+ - name: π¦ Install Dependencies
+ run: npx lerna@5 bootstrap --include-dependencies --scope ${{ inputs.scope }} --ignore-scripts -- --legacy-peer-deps
+ shell: bash
+ working-directory: ${{ inputs.working-directory }}
+ - name: π·οΈ Set Version
+ run: |
+ if [ -z "${{ inputs.preid }}" ]; then
+ npx lerna@5 version ${{ inputs.version }} --yes --exact --no-changelog --no-push --no-git-tag-version
+ else
+ npx lerna@5 version ${{ inputs.version }} --yes --exact --no-changelog --no-push --no-git-tag-version --preid=${{ inputs.preid }}
+ fi
+ shell: bash
+ working-directory: ${{ inputs.working-directory }}
+ - name: ποΈ Run Build
+ run: npm run build
+ shell: bash
+ working-directory: ${{ inputs.working-directory }}
+ - name: π Publish to NPM
+ run: npm publish ${{ inputs.folder }} --tag ${{ inputs.tag }} --provenance
+ shell: bash
+ working-directory: ${{ inputs.working-directory }}
+
diff --git a/.github/assets/logo.png b/.github/assets/logo.png
new file mode 100644
index 00000000000..42c5f25bfae
Binary files /dev/null and b/.github/assets/logo.png differ
diff --git a/.github/ionic-issue-bot.yml b/.github/ionic-issue-bot.yml
new file mode 100644
index 00000000000..d98b2a53205
--- /dev/null
+++ b/.github/ionic-issue-bot.yml
@@ -0,0 +1,145 @@
+triage:
+ label: triage
+ removeLabelWhenProjectAssigned: true
+ dryRun: false
+
+comment:
+ labels:
+ - label: "help wanted"
+ message: >
+ This issue has been labeled as `help wanted`. This label is added to issues
+ that we believe would be good for contributors.
+
+
+ If you'd like to work on this issue, please comment here letting us know that
+ you would like to submit a pull request for it. This helps us to keep track of
+ the pull request and make sure there isn't duplicated effort.
+
+
+ For a guide on how to create a pull request and test this project locally to see
+ your changes, see our [contributing documentation](https://ionicframework.com/docs/building/contributing).
+
+
+ Thank you!
+ - label: "ionitron: needs reproduction"
+ message: >
+ Thanks for the issue! This issue has been labeled as `needs reproduction`. This label
+ is added to issues that need a code reproduction.
+
+
+ Please reproduce this issue in an Ionic starter application and provide a way for us to access it (GitHub repo, StackBlitz, etc). Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed.
+
+
+ If you have already provided a code snippet and are seeing this message, it is likely that the code snippet was not enough for our team to reproduce the issue.
+
+
+ For a guide on how to create a good reproduction, see our [Contributing Guide](https://ionicframework.com/docs/contributing/how-to-contribute#creating-a-good-code-reproduction).
+ - label: "community feedback wanted"
+ message: >
+ This issue has been labeled as `community feedback wanted`. This label is added to issues that we would like to hear from the community on before moving forward with any final decision on the feature request.
+
+
+ If the requested feature is something you would find useful for your applications, please react to the original post with π (`+1`). If you would like to provide an additional use case for the feature, please post a comment.
+
+
+ The team will review this feedback and make a final decision. Any decision will be posted on this thread, but please note that we may ultimately decide not to pursue this feature.
+
+
+ Thank you!
+ dryRun: false
+
+closeAndLock:
+ labels:
+ - label: "ionitron: support"
+ message: >
+ Thanks for the issue! This issue appears to be a support request. We use this issue tracker exclusively for
+ bug reports and feature requests. Please use our [forum](https://forum.ionicframework.com) for questions about the framework.
+
+
+ Thank you for using Ionic!
+ - label: "ionitron: missing template"
+ message: >
+ Thanks for the issue! It appears that you have not filled out the provided issue template. We use this issue
+ template in order to gather more information and further assist you. Please create a new issue and ensure the
+ template is fully filled out.
+
+
+ Thank you for using Ionic!
+ close: true
+ lock: true
+ dryRun: false
+
+lockClosed:
+ days: 30
+ maxIssuesPerRun: 100
+ message: >
+ Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue.
+ If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.
+ dryRun: false
+
+stale:
+ days: 365
+ maxIssuesPerRun: 100
+ exemptLabels:
+ - "good first issue"
+ - "triage"
+ - "bug: external"
+ - "type: bug"
+ - "type: feature request"
+ - "needs: investigation"
+ exemptAssigned: true
+ exemptProjects: true
+ exemptMilestones: true
+ label: "ionitron: stale issue"
+ message: >
+ Thanks for the issue! This issue is being closed due to inactivity. If this is still
+ an issue with the latest version of Ionic, please create a new issue and ensure the
+ template is fully filled out.
+
+
+ Thank you for using Ionic!
+ close: true
+ lock: true
+ dryRun: false
+
+noReply:
+ days: 14
+ maxIssuesPerRun: 100
+ label: "needs: reply"
+ responseLabel: triage
+ exemptProjects: true
+ exemptMilestones: true
+ message: >
+ Thanks for the issue! This issue is being closed due to the lack of a reply. If this is still
+ an issue with the latest version of Ionic, please create a new issue and ensure the
+ template is fully filled out.
+
+
+ Thank you for using Ionic!
+ close: true
+ lock: true
+ dryRun: false
+
+noReproduction:
+ days: 14
+ maxIssuesPerRun: 100
+ label: "ionitron: needs reproduction"
+ responseLabel: triage
+ exemptProjects: true
+ exemptMilestones: true
+ message: >
+ Thanks for the issue! This issue is being closed due to the lack of a code reproduction. If this is still
+ an issue with the latest version of Ionic, please create a new issue and ensure the
+ template is fully filled out.
+
+
+ Thank you for using Ionic!
+ close: true
+ lock: true
+ dryRun: false
+
+screenshot:
+ appId: 18001
+ checkName: "build"
+ baseUrl: "https://screenshot.ionicframework.com"
+ dryRun: false
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 00000000000..7ea70aaf7f1
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,22 @@
+# This is used with the label workflow which
+# will triage pull requests and apply a label based on the
+# paths that are modified in the pull request.
+#
+# For more information, see:
+# https://github.com/actions/labeler
+
+'package: core':
+- changed-files:
+ - any-glob-to-any-file: ['core/**/*']
+
+'package: angular':
+- changed-files:
+ - any-glob-to-any-file: ['packages/angular/**/*', 'packages/angular-*/**/*']
+
+'package: react':
+- changed-files:
+ - any-glob-to-any-file: ['packages/react/**/*', 'packages/react-*/**/*']
+
+'package: vue':
+- changed-files:
+ - any-glob-to-any-file: ['packages/vue/**/*', 'packages/vue-*/**/*']
diff --git a/.github/workflows/actions/build-angular-server/action.yml b/.github/workflows/actions/build-angular-server/action.yml
new file mode 100644
index 00000000000..70c65316883
--- /dev/null
+++ b/.github/workflows/actions/build-angular-server/action.yml
@@ -0,0 +1,45 @@
+name: 'Build Ionic Angular Server'
+description: 'Build Ionic Angular Server'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: πΈοΈ Install Angular Server Dependencies
+ run: npm ci
+ shell: bash
+ working-directory: ./packages/angular-server
+ - name: π Sync
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/angular-server
+ - name: ποΈ Lint
+ run: npm run lint
+ shell: bash
+ working-directory: ./packages/angular-server
+ # No Typecheck step here. Unlike the rollup-built packages, which report
+ # type errors as warnings and still exit 0, ng-packagr fails the build on
+ # them, so the Build step below already covers this.
+ - name: ποΈ Build
+ run: npm run build.prod
+ shell: bash
+ working-directory: ./packages/angular-server
+ - name: Clean core package.json
+ run: git checkout ./package.json
+ shell: bash
+ working-directory: ./core
+ - name: π Check Diff
+ run: git diff --exit-code
+ shell: bash
+ working-directory: ./packages/angular-server
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-angular-server
+ output: packages/angular-server/AngularServerBuild.zip
+ paths: packages/angular-server/dist
diff --git a/.github/workflows/actions/build-angular/action.yml b/.github/workflows/actions/build-angular/action.yml
new file mode 100644
index 00000000000..121fa6f7da9
--- /dev/null
+++ b/.github/workflows/actions/build-angular/action.yml
@@ -0,0 +1,45 @@
+name: 'Build Ionic Angular'
+description: 'Build Ionic Angular'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: πΈοΈ Install Angular Dependencies
+ run: npm ci
+ shell: bash
+ working-directory: ./packages/angular
+ - name: π Sync
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/angular
+ - name: ποΈ Lint
+ run: npm run lint
+ shell: bash
+ working-directory: ./packages/angular
+ # No Typecheck step here. Unlike the rollup-built packages, which report
+ # type errors as warnings and still exit 0, ng-packagr fails the build on
+ # them, so the Build step below already covers this.
+ - name: ποΈ Build
+ run: npm run build
+ shell: bash
+ working-directory: ./packages/angular
+ - name: Clean core package.json
+ run: git checkout ./package.json
+ shell: bash
+ working-directory: ./core
+ - name: π Check Diff
+ run: git diff --exit-code
+ shell: bash
+ working-directory: ./packages/angular
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-angular
+ output: ./packages/angular/AngularBuild.zip
+ paths: ./packages/angular/dist ./packages/angular/css
diff --git a/.github/workflows/actions/build-core-stencil-prerelease/action.yml b/.github/workflows/actions/build-core-stencil-prerelease/action.yml
new file mode 100644
index 00000000000..f1e33c8b55e
--- /dev/null
+++ b/.github/workflows/actions/build-core-stencil-prerelease/action.yml
@@ -0,0 +1,52 @@
+name: 'Build Ionic Core with Stencil Prerelease'
+description: 'Build Ionic Core with a Prerelease Build of Stencil'
+inputs:
+ stencil-core-version:
+ description: 'The NPM tag of @stencil/core to install.'
+ type: string
+ required: true
+ stencil-react-output-target-version:
+ description: 'The NPM tag of @stencil/react-output-target to install.'
+ type: string
+ required: true
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+
+ - name: πΈοΈ Install Core Dependencies
+ run: npm ci
+ working-directory: ./core
+ shell: bash
+ - name: π¦ Install Stencil Core ${{ inputs.stencil-core-version }}
+ working-directory: ./core
+ run: npm i @stencil/core@${{ inputs.stencil-core-version }}
+ shell: bash
+ - name: π¦ Install Stencil React Output Target ${{ inputs.stencil-react-output-target-version }}
+ working-directory: ./core
+ run: npm i @stencil/react-output-target@${{ inputs.stencil-react-output-target-version }}
+ shell: bash
+ - name: ποΈ Build Core
+ run: npm run build -- --ci --debug --verbose
+ working-directory: ./core
+ shell: bash
+ - name: π Verify Lazy Imports
+ run: npm run test.lazy-imports
+ working-directory: ./core
+ shell: bash
+ - name: πΈοΈ Install React Package Dependencies
+ run: npm ci
+ working-directory: ./packages/react
+ shell: bash
+ - name: ποΈ Lint React Package
+ run: npm run lint
+ working-directory: ./packages/react
+ shell: bash
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-core
+ output: core/CoreBuild.zip
+ paths: core/dist core/components core/css core/hydrate core/loader core/src/components.d.ts core/package.json packages/react/src/components/components.ts
diff --git a/.github/workflows/actions/build-core/action.yml b/.github/workflows/actions/build-core/action.yml
new file mode 100644
index 00000000000..068eab5c471
--- /dev/null
+++ b/.github/workflows/actions/build-core/action.yml
@@ -0,0 +1,40 @@
+name: 'Build Ionic Core'
+description: 'Build Ionic Core'
+inputs:
+ ionicons-version:
+ description: 'The NPM tag of ionicons to install.'
+ type: string
+ required: false
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - name: πΈοΈ Install Dependencies
+ run: npm install
+ working-directory: ./core
+ shell: bash
+ # If an Ionicons version was specified install that.
+ # Otherwise just use the version defined in the package.json.
+ - name: π¦ Install Ionicons Version
+ if: inputs.ionicons-version != ''
+ run: npm install ionicons@${{ inputs.ionicons-version }}
+ working-directory: ./core
+ shell: bash
+ - name: ποΈ Build Core
+ run: npm run build -- --ci
+ working-directory: ./core
+ shell: bash
+ - name: π Verify Lazy Imports
+ run: npm run test.lazy-imports
+ working-directory: ./core
+ shell: bash
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-core
+ output: core/CoreBuild.zip
+ # Include generated proxy files from Stencil output targets so
+ # framework builds can detect when they need to be updated
+ paths: core/dist core/components core/css core/hydrate core/loader core/src/components.d.ts core/api.txt packages/angular/src/lazy/directives/proxies.ts packages/angular/src/lazy/directives/proxies-list.ts packages/angular/src/standalone/directives packages/vue/src/proxies.ts packages/react/src/components/proxies.ts packages/react/src/components/inner-proxies.ts packages/react/src/components/routing-proxies.ts
diff --git a/.github/workflows/actions/build-react-router/action.yml b/.github/workflows/actions/build-react-router/action.yml
new file mode 100644
index 00000000000..bb2cbb27eea
--- /dev/null
+++ b/.github/workflows/actions/build-react-router/action.yml
@@ -0,0 +1,51 @@
+name: 'Build Ionic React Router'
+description: 'Build Ionic React Router'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-react
+ path: ./packages/react
+ filename: ReactBuild.zip
+ - name: πΈοΈ Install Dependencies
+ run: npm ci
+ shell: bash
+ working-directory: ./packages/react-router
+ - name: π Sync
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/react-router
+ - name: ποΈ Lint
+ run: npm run lint
+ shell: bash
+ working-directory: ./packages/react-router
+ # The rollup build reports type errors as warnings and still succeeds, so
+ # this step is what keeps the package type-clean.
+ - name: π Typecheck
+ run: npm run typecheck
+ shell: bash
+ working-directory: ./packages/react-router
+ - name: ποΈ Build
+ run: npm run build
+ shell: bash
+ working-directory: ./packages/react-router
+ - name: Clean package.json updates
+ run: git checkout ./core/package.json ./packages/react/package.json
+ shell: bash
+ - name: π Check Diff
+ run: git diff --exit-code
+ shell: bash
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-react-router
+ output: packages/react-router/ReactRouterBuild.zip
+ paths: packages/react-router/dist
diff --git a/.github/workflows/actions/build-react/action.yml b/.github/workflows/actions/build-react/action.yml
new file mode 100644
index 00000000000..b7d3ac6b99a
--- /dev/null
+++ b/.github/workflows/actions/build-react/action.yml
@@ -0,0 +1,50 @@
+name: 'Build Ionic React'
+description: 'Build Ionic React'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: πΈοΈ Install React Dependencies
+ run: npm ci
+ shell: bash
+ working-directory: ./packages/react
+ - name: π Sync
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/react
+ - name: ποΈ Lint
+ run: npm run lint
+ shell: bash
+ working-directory: ./packages/react
+ # The rollup build reports type errors as warnings and still succeeds, so
+ # this step is what keeps the package type-clean.
+ - name: π Typecheck
+ run: npm run typecheck
+ shell: bash
+ working-directory: ./packages/react
+ - name: ποΈ Build
+ run: npm run build
+ shell: bash
+ working-directory: ./packages/react
+ - name: π§ͺ Test Spec
+ run: npm run test.spec
+ shell: bash
+ working-directory: ./packages/react
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-react
+ output: packages/react/ReactBuild.zip
+ paths: packages/react/dist packages/react/css packages/react/package.json
+ - name: Clean package.json updates
+ run: git checkout ./core/package.json ./packages/react/package.json
+ shell: bash
+ - name: π Check Diff
+ run: git diff --exit-code
+ shell: bash
diff --git a/.github/workflows/actions/build-vue-router/action.yml b/.github/workflows/actions/build-vue-router/action.yml
new file mode 100644
index 00000000000..68972ef2324
--- /dev/null
+++ b/.github/workflows/actions/build-vue-router/action.yml
@@ -0,0 +1,51 @@
+name: 'Build Ionic Vue Router'
+description: 'Builds Ionic Vue Router'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-vue
+ path: ./packages/vue
+ filename: VueBuild.zip
+ - name: πΈοΈ Install Vue Router Dependencies
+ run: npm ci
+ shell: bash
+ working-directory: ./packages/vue-router
+ - name: π Sync
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/vue-router
+ - name: ποΈ Lint
+ run: npm run lint
+ shell: bash
+ working-directory: ./packages/vue-router
+ # The rollup build reports type errors as warnings and still succeeds, so
+ # this step is what keeps the package type-clean.
+ - name: π Typecheck
+ run: npm run typecheck
+ shell: bash
+ working-directory: ./packages/vue-router
+ - name: ποΈ Build
+ run: npm run build
+ shell: bash
+ working-directory: ./packages/vue-router
+ - name: Clean package.json updates
+ run: git checkout ./core/package.json
+ shell: bash
+ - name: π Check Diff
+ run: git diff --exit-code
+ shell: bash
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-vue-router
+ output: ./packages/vue-router/VueRouterBuild.zip
+ paths: packages/vue-router/dist
diff --git a/.github/workflows/actions/build-vue/action.yml b/.github/workflows/actions/build-vue/action.yml
new file mode 100644
index 00000000000..22c731e99ad
--- /dev/null
+++ b/.github/workflows/actions/build-vue/action.yml
@@ -0,0 +1,46 @@
+name: 'Build Ionic Vue'
+description: 'Build Ionic Vue'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: πΈοΈ Install Vue Dependencies
+ run: npm ci
+ shell: bash
+ working-directory: ./packages/vue
+ - name: π Sync
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/vue
+ - name: ποΈ Lint
+ run: npm run lint
+ shell: bash
+ working-directory: ./packages/vue
+ # The rollup build reports type errors as warnings and still succeeds, so
+ # this step is what keeps the package type-clean.
+ - name: π Typecheck
+ run: npm run typecheck
+ shell: bash
+ working-directory: ./packages/vue
+ - name: ποΈ Build
+ run: npm run build
+ shell: bash
+ working-directory: ./packages/vue
+ - name: Clean package.json updates
+ run: git checkout ./core/package.json ./packages/react/package.json
+ shell: bash
+ - name: π Check Diff
+ run: git diff --exit-code
+ shell: bash
+ - uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-vue
+ output: packages/vue/VueBuild.zip
+ paths: packages/vue/dist packages/vue/css
diff --git a/.github/workflows/actions/download-archive/action.yml b/.github/workflows/actions/download-archive/action.yml
new file mode 100644
index 00000000000..97f07f8627a
--- /dev/null
+++ b/.github/workflows/actions/download-archive/action.yml
@@ -0,0 +1,19 @@
+name: 'Ionic Framework Archive Download'
+description: 'Downloads and decompresses an archive from a previous job'
+inputs:
+ path:
+ description: 'Input archive name'
+ filename:
+ description: 'Input file name'
+ name:
+ description: 'Archive name'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ inputs.name }}
+ path: ${{ inputs.path }}
+ - name: π Extract Archive
+ run: unzip -q -o ${{ inputs.path }}/${{ inputs.filename }}
+ shell: bash
diff --git a/.github/workflows/actions/test-angular-e2e/action.yml b/.github/workflows/actions/test-angular-e2e/action.yml
new file mode 100644
index 00000000000..8a15bfb5037
--- /dev/null
+++ b/.github/workflows/actions/test-angular-e2e/action.yml
@@ -0,0 +1,46 @@
+name: 'Test Angular E2E'
+description: 'Test Angular E2E'
+inputs:
+ app:
+ description: 'The specific test application'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-angular
+ path: ./packages/angular
+ filename: AngularBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-angular-server
+ path: ./packages/angular-server
+ filename: AngularServerBuild.zip
+ - name: π§ͺ Create Test App
+ run: ./build.sh ${{ inputs.app }}
+ shell: bash
+ working-directory: ./packages/angular/test
+ - name: πΈοΈ Install Dependencies
+ run: npm install
+ shell: bash
+ working-directory: ./packages/angular/test/build/${{ inputs.app }}
+ - name: π¦ Install Playwright Browsers
+ run: npx playwright install chromium
+ shell: bash
+ working-directory: ./packages/angular/test/build/${{ inputs.app }}
+ - name: π Sync Built Changes
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/angular/test/build/${{ inputs.app }}
+ - name: π§ͺ Run Tests
+ run: npm run test
+ shell: bash
+ working-directory: ./packages/angular/test/build/${{ inputs.app }}
diff --git a/.github/workflows/actions/test-core-clean-build/action.yml b/.github/workflows/actions/test-core-clean-build/action.yml
new file mode 100644
index 00000000000..f6275acbee5
--- /dev/null
+++ b/.github/workflows/actions/test-core-clean-build/action.yml
@@ -0,0 +1,30 @@
+name: 'Test Core Clean Build'
+description: 'Test Core Clean Build'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: Clean package.json updates
+ run: git checkout ./core/package.json ./packages/react/package.json
+ shell: bash
+ - name: π Check Diff
+ run: |
+ git diff --exit-code || {
+ echo -e "\033[1;31mβ οΈ Error: Differences Detected β οΈ\033[0m"
+ echo -e "\033[1;31mThere are uncommitted changes between the build outputs from CI and your branch.\033[0m"
+ echo -e "\033[1;31mPlease ensure you have followed these steps:\033[0m"
+ echo -e "\033[1;31m1. Run 'npm run build' locally to generate the latest build output.\033[0m"
+ echo -e "\033[1;31m2. Commit and push all necessary changes to your branch.\033[0m"
+ echo -e "\033[1;31m3. Compare and validate the differences before proceeding.\033[0m"
+ exit 1
+ }
+ shell: bash
+ working-directory: ./core
diff --git a/.github/workflows/actions/test-core-lint/action.yml b/.github/workflows/actions/test-core-lint/action.yml
new file mode 100644
index 00000000000..66a3036ef04
--- /dev/null
+++ b/.github/workflows/actions/test-core-lint/action.yml
@@ -0,0 +1,27 @@
+name: 'Test Core Lint'
+description: 'Test Core Lint'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - name: πΈοΈ Install Dependencies
+ run: npm ci
+ working-directory: ./core
+ shell: bash
+ - name: Clean core package.json
+ run: git checkout ./package.json
+ shell: bash
+ working-directory: ./core
+ - name: ποΈ Lint
+ run: npm run lint
+ shell: bash
+ working-directory: ./core
+ # Lint changes should be pushed
+ # to the branch before the branch
+ # is merge eligible.
+ - name: π Check Lint Results
+ run: git diff --exit-code
+ shell: bash
+ working-directory: ./core
diff --git a/.github/workflows/actions/test-core-screenshot/action.yml b/.github/workflows/actions/test-core-screenshot/action.yml
new file mode 100644
index 00000000000..379ce934edc
--- /dev/null
+++ b/.github/workflows/actions/test-core-screenshot/action.yml
@@ -0,0 +1,85 @@
+name: 'Test Core Screenshot'
+description: 'Test Core Screenshot'
+inputs:
+ shard:
+ description: 'Playwright Test Shard (ex: 2)'
+ totalShards:
+ description: 'Playwright total number of test shards (ex: 4)'
+ update:
+ description: 'Whether or not to update the reference snapshots'
+ component:
+ description: 'The component to update the reference snapshots'
+
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: πΈοΈ Install Dependencies
+ run: npm install
+ shell: bash
+ working-directory: ./core
+ - name: π§ͺ Test
+ if: inputs.update != 'true'
+ run: npm run test.e2e.docker.ci ${{ inputs.component }} -- --shard=${{ inputs.shard }}/${{ inputs.totalShards }}
+ shell: bash
+ working-directory: ./core
+ - name: Clean core package.json
+ run: git checkout ./package.json
+ shell: bash
+ working-directory: ./core
+ - name: Test and Update
+ id: test-and-update
+ if: inputs.update == 'true'
+ # Keep track of the files that were
+ # changed so they can be correctly restored
+ # in the combine step.
+ # To do this, we move only the changed files
+ # to a separate directory, while preserving the
+ # directory structure of the source.
+ # When, we create and archive of these results
+ # so that the combine step can simply
+ # unzip and move the changed files into place.
+ # We have extra logic added so that job runners
+ # that do not have any new screenshots do not create
+ # an unnecessary .zip.
+ # Note that we need to unzip directory to be "core"
+ # which is why we not using the upload-archive
+ # composite step here.
+ run: |
+ npm run test.e2e.docker.ci ${{ inputs.component }} -- --shard=${{ inputs.shard }}/${{ inputs.totalShards }} --update-snapshots='changed'
+ git add src/\*.png --force
+ mkdir updated-screenshots
+ cd ../ && rsync -R --progress $(git diff --name-only --cached) core/updated-screenshots
+ if [ -d core/updated-screenshots/core ]; then
+ echo "hasUpdatedScreenshots=$(echo 'true')" >> $GITHUB_OUTPUT
+ cd core/updated-screenshots
+ zip -q -r ../../UpdatedScreenshots-${{ inputs.shard }}-${{ inputs.totalShards }}.zip core
+ fi
+ shell: bash
+ working-directory: ./core
+ - name: π¦ Archive Updated Screenshots
+ if: inputs.update == 'true' && steps.test-and-update.outputs.hasUpdatedScreenshots == 'true'
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: updated-screenshots-${{ inputs.shard }}-${{ inputs.totalShards }}
+ path: UpdatedScreenshots-${{ inputs.shard }}-${{ inputs.totalShards }}.zip
+ - name: π¦ Archive Test Results
+ # The always() ensures that this step
+ # runs even if the previous step fails.
+ # We want the test results to be archived
+ # even if the test fails in the previous
+ # step, otherwise there would be no way
+ # to debug these tests.
+ if: always()
+ uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: test-results-${{ inputs.shard }}-${{ inputs.totalShards }}
+ output: core/TestResults-${{ inputs.shard }}-${{ inputs.totalShards }}.zip
+ paths: core/playwright-report
diff --git a/.github/workflows/actions/test-core-spec/action.yml b/.github/workflows/actions/test-core-spec/action.yml
new file mode 100644
index 00000000000..f33ab0aa3e3
--- /dev/null
+++ b/.github/workflows/actions/test-core-spec/action.yml
@@ -0,0 +1,29 @@
+name: 'Test Core Spec'
+description: 'Test Core Spec'
+inputs:
+ stencil-version:
+ description: 'The NPM tag of @stencil/core to install.'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - name: πΈοΈ Install Dependencies
+ run: npm ci
+ working-directory: ./core
+ shell: bash
+ - name: π¦ Install Stencil ${{ inputs.stencil-version }}
+ run: npm install @stencil/core@${{ inputs.stencil-version }}
+ shell: bash
+ working-directory: ./core
+ if: ${{ inputs.stencil-version != '' }}
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: π§ͺ Test
+ run: npm run test.spec -- --ci
+ shell: bash
+ working-directory: ./core
diff --git a/.github/workflows/actions/test-react-e2e/action.yml b/.github/workflows/actions/test-react-e2e/action.yml
new file mode 100644
index 00000000000..af6100bdb07
--- /dev/null
+++ b/.github/workflows/actions/test-react-e2e/action.yml
@@ -0,0 +1,47 @@
+name: 'Test React E2E'
+description: 'Test React E2E'
+inputs:
+ app:
+ description: 'The specific test application'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-react
+ path: ./packages/react
+ filename: ReactBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-react-router
+ path: ./packages/react-router
+ filename: ReactRouterBuild.zip
+ - name: π§ͺ Create Test App
+ run: ./build.sh ${{ inputs.app }}
+ shell: bash
+ working-directory: ./packages/react/test
+ - name: πΈοΈ Install Dependencies
+ run: npm install
+ shell: bash
+ working-directory: ./packages/react/test/build/${{ inputs.app }}
+ - name: π Sync Built Changes
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/react/test/build/${{ inputs.app }}
+ - name: ποΈ Build
+ run: npm run build
+ shell: bash
+ working-directory: ./packages/react/test/build/${{ inputs.app }}
+ - name: π§ͺ Run Tests
+ run: npm run e2e
+ shell: bash
+ working-directory: ./packages/react/test/build/${{ inputs.app }}
+
diff --git a/.github/workflows/actions/test-react-router-e2e/action.yml b/.github/workflows/actions/test-react-router-e2e/action.yml
new file mode 100644
index 00000000000..beea150b549
--- /dev/null
+++ b/.github/workflows/actions/test-react-router-e2e/action.yml
@@ -0,0 +1,56 @@
+name: 'Test React Router E2E'
+description: 'Test React Router'
+inputs:
+ app:
+ description: 'The specific test application'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-react
+ path: ./packages/react
+ filename: ReactBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-react-router
+ path: ./packages/react-router
+ filename: ReactRouterBuild.zip
+ - name: π§ͺ Create Test App
+ run: ./build.sh ${{ inputs.app }}
+ shell: bash
+ working-directory: ./packages/react-router/test
+ - name: πΈοΈ Install Dependencies
+ run: npm install
+ shell: bash
+ working-directory: ./packages/react-router/test/build/${{ inputs.app }}
+ - name: π¦ Install Playwright Browsers
+ run: npx playwright install chromium
+ shell: bash
+ working-directory: ./packages/react-router/test/build/${{ inputs.app }}
+ - name: π Sync Built Changes
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/react-router/test/build/${{ inputs.app }}
+ - name: ποΈ Build
+ run: npm run build
+ shell: bash
+ working-directory: ./packages/react-router/test/build/${{ inputs.app }}
+ - name: π§ͺ Run Cypress Tests
+ run: npm run e2e
+ shell: bash
+ working-directory: ./packages/react-router/test/build/${{ inputs.app }}
+ - name: π Run Playwright Tests
+ run: npx playwright test --retries=2
+ env:
+ CI: true
+ shell: bash
+ working-directory: ./packages/react-router/test/build/${{ inputs.app }}
diff --git a/.github/workflows/actions/test-vue-e2e/action.yml b/.github/workflows/actions/test-vue-e2e/action.yml
new file mode 100644
index 00000000000..2316e019a46
--- /dev/null
+++ b/.github/workflows/actions/test-vue-e2e/action.yml
@@ -0,0 +1,56 @@
+name: 'Test Vue E2E'
+description: 'Test Vue E2E'
+inputs:
+ app:
+ description: 'The specific test application'
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-vue
+ path: ./packages/vue
+ filename: VueBuild.zip
+ - uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-vue-router
+ path: ./packages/vue-router
+ filename: VueRouterBuild.zip
+ - name: π§ͺ Create Test App
+ run: ./build.sh ${{ inputs.app }}
+ shell: bash
+ working-directory: ./packages/vue/test
+ - name: π¦ Install Dependencies
+ run: npm install
+ shell: bash
+ working-directory: ./packages/vue/test/build/${{ inputs.app }}
+ - name: π¦ Install Playwright Browsers
+ run: npx playwright install chromium
+ shell: bash
+ working-directory: ./packages/vue/test/build/${{ inputs.app }}
+ - name: π Sync
+ run: npm run sync
+ shell: bash
+ working-directory: ./packages/vue/test/build/${{ inputs.app }}
+ - name: π§ͺ Run Spec Tests
+ run: npm run test:unit
+ shell: bash
+ working-directory: ./packages/vue/test/build/${{ inputs.app }}
+ - name: π§ͺ Run E2E Tests
+ run: npm run test:e2e
+ shell: bash
+ working-directory: ./packages/vue/test/build/${{ inputs.app }}
+ - name: π Run Playwright Tests
+ run: npx playwright test --retries=2
+ env:
+ CI: true
+ shell: bash
+ working-directory: ./packages/vue/test/build/${{ inputs.app }}
diff --git a/.github/workflows/actions/update-reference-screenshots/action.yml b/.github/workflows/actions/update-reference-screenshots/action.yml
new file mode 100644
index 00000000000..844f4e6d2ac
--- /dev/null
+++ b/.github/workflows/actions/update-reference-screenshots/action.yml
@@ -0,0 +1,58 @@
+name: 'Update Reference Screenshots'
+description: 'Update Reference Screenshots'
+
+on:
+ workflow_dispatch:
+
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ path: ./artifacts
+ - name: π Extract Archives
+ # This finds all .zip files in the ./artifacts
+ # directory, including nested directories.
+ # It then unzips every .zip to the root directory
+ run: |
+ find . -type f -name 'UpdatedScreenshots-*.zip' -exec unzip -q -o -d ../ {} \;
+ shell: bash
+ working-directory: ./artifacts
+ - name: Clean core package.json
+ run: git checkout ./package.json
+ shell: bash
+ working-directory: ./core
+ - name: πΈ Push Screenshots
+ # Configure user as Ionitron
+ # and push only the changed .png snapshots
+ # to the remote branch.
+ # Non-Linux screenshots are in .gitignore
+ # to prevent local screenshots from getting
+ # pushed to Git.
+ run: |
+ git config user.name ionitron
+ git config user.email hi@ionicframework.com
+
+ # This adds an empty entry for new
+ # screenshot files so we can track them with
+ # git diff
+ git add src/\*.png -N
+
+ if git diff --exit-code; then
+ echo -e "\033[1;31mβ οΈ Error: No new screenshots generated β οΈ\033[0m"
+ echo -e "\033[1;31mThis means that there were zero visual diffs when running screenshot tests.\033[0m"
+ echo -e "\033[1;31mMake sure you have pushed any code changes that would result in visual diffs.\033[0m"
+ exit 1
+ else
+ # This actually adds the contents
+ # of the screenshots (including new ones)
+ git add src/\*.png
+ git commit -m "chore(): add updated snapshots"
+ git push
+ fi
+
+ shell: bash
+ working-directory: ./core
diff --git a/.github/workflows/actions/upload-archive/action.yml b/.github/workflows/actions/upload-archive/action.yml
new file mode 100644
index 00000000000..3cfff8853fc
--- /dev/null
+++ b/.github/workflows/actions/upload-archive/action.yml
@@ -0,0 +1,19 @@
+name: 'Ionic Framework Archive Upload'
+description: 'Compresses and uploads an archive to be reused across jobs'
+inputs:
+ paths:
+ description: 'Paths to files or directories to archive'
+ output:
+ description: 'Output file name'
+ name:
+ description: 'Archive name'
+runs:
+ using: 'composite'
+ steps:
+ - name: ποΈ Create Archive
+ run: zip -q -r ${{ inputs.output }} ${{ inputs.paths }}
+ shell: bash
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ${{ inputs.name }}
+ path: ${{ inputs.output }}
diff --git a/.github/workflows/assign-issues.yml b/.github/workflows/assign-issues.yml
new file mode 100644
index 00000000000..fc254b78976
--- /dev/null
+++ b/.github/workflows/assign-issues.yml
@@ -0,0 +1,18 @@
+name: Assign issues to triage
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ auto-assign:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - name: 'Auto-assign issue'
+ uses: pozil/auto-assign-issue@af6beea6bdf1e8eb373f061c5bc168681fc6d011 # v4.0.1
+ with:
+ assignees: brandyscarney, thetaPC, ShaneK
+ numOfAssignee: 1
+ allowSelfAssign: false
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000000..90d65ee18f0
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,217 @@
+name: 'Ionic Framework Build'
+
+on:
+ pull_request:
+ branches: [ '**' ]
+ merge_group:
+ workflow_dispatch:
+ inputs:
+ ionicons_npm_release_tag:
+ required: false
+ type: string
+ description: What version of ionicons should be pulled from NPM? Use this if you want to test a custom version of Ionicons with Ionic.
+
+# When pushing a new commit we should
+# cancel the previous test run to not
+# consume more runners than we need to.
+concurrency:
+ group: ${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-core:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-core
+ with:
+ ionicons-version: ${{ inputs.ionicons_npm_release_tag }}
+
+ test-core-clean-build:
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-clean-build
+
+ test-core-lint:
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-lint
+
+ test-core-spec:
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-spec
+
+ test-core-screenshot:
+ strategy:
+ matrix:
+ # Divide the tests into n buckets
+ # and run those buckets in parallel.
+ # To increase the number of shards,
+ # add new items to the shard array
+ # and change the value of totalShards
+ # to be the length of the shard array.
+ shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
+ totalShards: [20]
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-screenshot
+ with:
+ shard: ${{ matrix.shard }}
+ totalShards: ${{ matrix.totalShards }}
+
+ # Screenshots are required to pass
+ # in order for the branch to be merge
+ # eligible. However, the screenshot tests
+ # are run on n runners where n can change
+ # over time. The verify-screenshots step allows
+ # us to have a required status check for screenshot
+ # results without having to manually add each
+ # matrix run in the branch protection rules
+ # Source: https://github.community/t/status-check-for-a-matrix-jobs/127354
+ verify-screenshots:
+ if: ${{ always() }}
+ needs: test-core-screenshot
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-core-screenshot.result != 'success' }}
+ run: exit 1
+
+ build-vue:
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-vue
+
+ build-vue-router:
+ needs: [build-vue]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-vue-router
+
+ test-vue-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [vue3]
+ needs: [build-vue, build-vue-router]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-vue-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-vue-e2e:
+ if: ${{ always() }}
+ needs: test-vue-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-vue-e2e.result != 'success' }}
+ run: exit 1
+
+ build-angular:
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-angular
+
+ build-angular-server:
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-angular-server
+
+ test-angular-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [ng18, ng19, ng20, ng21, ng22]
+ needs: [build-angular, build-angular-server]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-angular-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-angular-e2e:
+ if: ${{ always() }}
+ needs: test-angular-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-angular-e2e.result != 'success' }}
+ run: exit 1
+
+ build-react:
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-react
+
+ build-react-router:
+ needs: [build-react]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-react-router
+
+ test-react-router-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [reactrouter6-react18, reactrouter6-react19]
+ needs: [build-react, build-react-router]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-react-router-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-react-router-e2e:
+ if: ${{ always() }}
+ needs: test-react-router-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-react-router-e2e.result != 'success' }}
+ run: exit 1
+
+ test-react-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [react18, react19]
+ needs: [build-react, build-react-router]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-react-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-react-e2e:
+ if: ${{ always() }}
+ needs: test-react-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-react-e2e.result != 'success' }}
+ run: exit 1
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000000..df238d50502
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,21 @@
+name: CodeQL
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ analyze:
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
+ with:
+ languages: javascript
+ - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
diff --git a/.github/workflows/conventional-commit.yml b/.github/workflows/conventional-commit.yml
new file mode 100644
index 00000000000..65c026de5eb
--- /dev/null
+++ b/.github/workflows/conventional-commit.yml
@@ -0,0 +1,43 @@
+name: PR Conventional Commit Validation
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, edited]
+
+jobs:
+ validate-pr-title:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Validate PR title
+ if: |
+ !contains(github.event.pull_request.title, 'release') &&
+ !contains(github.event.pull_request.title, 'chore')
+ uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ # Configure that a scope must always be provided.
+ requireScope: true
+ # Configure allowed commit types
+ types: |
+ feat
+ fix
+ docs
+ style
+ refactor
+ perf
+ test
+ build
+ ci
+ revert
+ release
+ chore
+ # Configure additional validation for the subject based on a regex.
+ # This example ensures the subject doesn't start with an uppercase character.
+ subjectPattern: ^(?![A-Z]).+$
+ # If `subjectPattern` is configured, you can use this property to
+ # override the default error message that is shown when the pattern
+ # doesn't match. The variables `subject` and `title` can be used
+ # within the message.
+ subjectPatternError: |
+ The subject "{subject}" found in the pull request title "{title}" didn't match the configured pattern. Please ensure that the subject doesn't start with an uppercase character.
diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml
new file mode 100644
index 00000000000..29b499e1855
--- /dev/null
+++ b/.github/workflows/dev-build.yml
@@ -0,0 +1,45 @@
+name: 'Ionic Dev Build'
+
+on:
+ workflow_call:
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ create-dev-hash:
+ runs-on: ubuntu-latest
+ outputs:
+ dev-hash: ${{ steps.create-dev-hash.outputs.DEV_HASH }}
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ # A 1 is required before the timestamp
+ # as lerna will fail when there is a leading 0
+ # See https://github.com/lerna/lerna/issues/2840
+ - name: Install Dependencies
+ run: npm ci
+ shell: bash
+ - id: create-dev-hash
+ name: Create Dev Hash
+ run: |
+ echo "DEV_HASH=$(node ./.scripts/bump-version.js)-dev.1$(date +%s).1$(git log -1 --format=%H | cut -c 1-7)" >> $GITHUB_OUTPUT
+ shell: bash
+
+ release-ionic:
+ needs: [create-dev-hash]
+ permissions:
+ contents: read
+ id-token: write
+ uses: ./.github/workflows/release-ionic.yml
+ with:
+ tag: dev
+ version: ${{ needs.create-dev-hash.outputs.dev-hash }}
+
+ get-build:
+ name: Get your dev build!
+ runs-on: ubuntu-latest
+ needs: [create-dev-hash, release-ionic]
+ steps:
+ - run: echo ${{ needs.create-dev-hash.outputs.dev-hash }}
+
diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml
new file mode 100644
index 00000000000..49606e47e90
--- /dev/null
+++ b/.github/workflows/label.yml
@@ -0,0 +1,19 @@
+# This workflow will triage pull requests and apply a label based on the
+# paths that are modified in the pull request.
+#
+# To use this workflow, you will need to set up a .github/labeler.yml
+# file with configuration. For more information, see:
+# https://github.com/actions/labeler
+
+name: "Pull Request Labeler"
+on:
+- pull_request_target
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ sync-labels: true
diff --git a/.github/workflows/migrate.yml b/.github/workflows/migrate.yml
new file mode 100644
index 00000000000..54990664544
--- /dev/null
+++ b/.github/workflows/migrate.yml
@@ -0,0 +1,93 @@
+name: 'Ionic Migrate'
+
+# Lint, test, and build the @ionic/migrate CLI.
+#
+# The workflow always triggers (no trigger-level `paths` filter) so the
+# `verify-migrate` gate always reports a status and can be marked a required
+# check: a path-filtered *trigger* would skip the whole workflow on unrelated
+# PRs, and a required check that never runs blocks merges forever. Instead the
+# `changes` job detects whether the package was touched and the heavy job runs
+# only then; `verify-migrate` reports success either way (skipped == fine).
+on:
+ pull_request:
+ branches: [ '**' ]
+ merge_group:
+ workflow_dispatch:
+
+# Lint/test/build only reads the repo; keep the token least-privilege.
+permissions:
+ contents: read
+
+concurrency:
+ group: migrate-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ changes:
+ runs-on: ubuntu-latest
+ outputs:
+ migrate: ${{ steps.filter.outputs.migrate }}
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ # Full history so the PR base commit is reachable for the diff below.
+ fetch-depth: 0
+ - name: Detect migrate changes
+ id: filter
+ shell: bash
+ run: |
+ # Fail the step (not the else branch) if git diff itself errors, so a
+ # broken detector can't masquerade as "package untouched".
+ set -euo pipefail
+ # Only pull_request runs get the path optimization. merge_group and
+ # manual dispatch run the (fast) tests unconditionally, so the merge
+ # queue never blocks on a check that was skipped by a stale diff.
+ if [ "${{ github.event_name }}" != "pull_request" ]; then
+ echo "migrate=true" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ base="${{ github.event.pull_request.base.sha }}"
+ changed="$(git diff --name-only "$base" HEAD)"
+ if echo "$changed" \
+ | grep -qE '^(packages/migrate/|\.github/workflows/migrate\.yml$)'; then
+ echo "migrate=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "migrate=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ test-migrate:
+ needs: changes
+ if: ${{ needs.changes.outputs.migrate == 'true' }}
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./packages/migrate
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ cache: 'npm'
+ cache-dependency-path: packages/migrate/package-lock.json
+ - name: Install Dependencies
+ run: npm ci
+ - name: Lint
+ run: npm run lint
+ - name: Test
+ run: npm test
+ - name: Build
+ run: npm run build
+
+ # Always-running required gate. See header comment for why this exists.
+ # Green when the package was untouched (test-migrate skipped) or tests pass;
+ # red when detection failed or the tests actually failed.
+ verify-migrate:
+ if: ${{ always() }}
+ needs: [ changes, test-migrate ]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check results
+ # changes.result != 'success' catches a failed/cancelled detector,
+ # whose skipped test-migrate would otherwise slip through as green.
+ if: ${{ needs.changes.result != 'success' || needs.test-migrate.result == 'failure' || needs.test-migrate.result == 'cancelled' }}
+ run: exit 1
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
new file mode 100644
index 00000000000..3f81651868f
--- /dev/null
+++ b/.github/workflows/nightly.yml
@@ -0,0 +1,40 @@
+name: 'Ionic Nightly Build'
+
+on:
+ workflow_call:
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ create-nightly-hash:
+ runs-on: ubuntu-latest
+ outputs:
+ nightly-hash: ${{ steps.create-nightly-hash.outputs.NIGHTLY_HASH }}
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ # A 1 is required before the timestamp
+ # as lerna will fail when there is a leading 0
+ # See https://github.com/lerna/lerna/issues/2840
+ - name: Install Dependencies
+ run: npm ci
+ shell: bash
+ - id: create-nightly-hash
+ name: Create Nightly Hash
+ # The date should output YYYYMMDD
+ # so that it is human readable
+ run: |
+ echo "NIGHTLY_HASH=$(node ./.scripts/bump-version.js)-nightly.$(date +%Y%m%d)" >> $GITHUB_OUTPUT
+ shell: bash
+
+ release-ionic:
+ needs: [create-nightly-hash]
+ permissions:
+ contents: read
+ id-token: write
+ uses: ./.github/workflows/release-ionic.yml
+ secrets: inherit
+ with:
+ tag: nightly
+ version: ${{ needs.create-nightly-hash.outputs.nightly-hash }}
diff --git a/.github/workflows/release-ionic.yml b/.github/workflows/release-ionic.yml
new file mode 100644
index 00000000000..e8b7030966d
--- /dev/null
+++ b/.github/workflows/release-ionic.yml
@@ -0,0 +1,209 @@
+name: 'Release Ionic'
+
+on:
+ workflow_call:
+ inputs:
+ version:
+ description: 'The type of version to release.'
+ type: string
+ required: true
+ tag:
+ description: 'The tag to publish to NPM.'
+ type: string
+ required: true
+ preid:
+ description: 'The prerelease identifier used when doing a prerelease.'
+ type: string
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ release-core:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/core'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'core'
+ - name: Cache Built @ionic/core
+ uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-core
+ output: core/CoreBuild.zip
+ paths: core/dist core/components core/css core/hydrate core/loader core/src/components.d.ts
+ - name: Cache Built @ionic/docs
+ uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-docs
+ output: packages/docs/DocsBuild.zip
+ paths: packages/docs/core.json packages/docs/core.d.ts
+
+ release-docs:
+ needs: [release-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Restore @ionic/docs built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-docs
+ path: ./packages/docs
+ filename: DocsBuild.zip
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/docs'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'packages/docs'
+
+ release-angular:
+ needs: [release-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Restore @ionic/core built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/angular'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'packages/angular'
+ - name: Cache Built @ionic/angular
+ uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-angular
+ output: packages/angular/AngularBuild.zip
+ paths: packages/angular/dist packages/angular/css
+
+ release-react:
+ needs: [release-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Restore @ionic/core built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/react'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'packages/react'
+ - name: Cache Built @ionic/react
+ uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-react
+ output: packages/react/ReactBuild.zip
+ paths: packages/react/dist packages/react/css
+
+ release-vue:
+ needs: [release-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Restore @ionic/core built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/vue'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'packages/vue'
+ - name: Cache Built @ionic/vue
+ uses: ./.github/workflows/actions/upload-archive
+ with:
+ name: ionic-vue
+ output: packages/vue/VueBuild.zip
+ paths: packages/vue/dist packages/vue/css
+
+ release-angular-server:
+ needs: [release-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Restore @ionic/core built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/angular-server'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'packages/angular-server'
+ folder: './dist'
+
+ release-react-router:
+ needs: [release-react]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Restore @ionic/core built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: Restore @ionic/react built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-react
+ path: ./packages/react
+ filename: ReactBuild.zip
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/react-router'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'packages/react-router'
+
+ release-vue-router:
+ needs: [release-vue]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Restore @ionic/core built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-core
+ path: ./core
+ filename: CoreBuild.zip
+ - name: Restore @ionic/vue built cache
+ uses: ./.github/workflows/actions/download-archive
+ with:
+ name: ionic-vue
+ path: ./packages/vue
+ filename: VueBuild.zip
+ - uses: ./.github/actions/publish-npm
+ with:
+ scope: '@ionic/vue-router'
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+ working-directory: 'packages/vue-router'
diff --git a/.github/workflows/release-migrate.yml b/.github/workflows/release-migrate.yml
new file mode 100644
index 00000000000..d119e6830de
--- /dev/null
+++ b/.github/workflows/release-migrate.yml
@@ -0,0 +1,72 @@
+name: 'Release @ionic/migrate'
+
+on:
+ workflow_call:
+ inputs:
+ tag:
+ description: 'Which npm tag should this be published to?'
+ required: true
+ type: string
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ release-migrate:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./packages/migrate
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: π’ Configure Node for Publish
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.x
+ registry-url: 'https://registry.npmjs.org'
+ scope: '@ionic'
+ cache: 'npm'
+ cache-dependency-path: packages/migrate/package-lock.json
+ - name: π¦ Install latest npm
+ run: npm install -g npm@latest
+ shell: bash
+ - name: πΈοΈ Install Dependencies
+ run: npm ci
+ shell: bash
+ - name: π Resolve Version
+ id: resolve-version
+ run: |
+ set -euo pipefail
+ version="$(node -p "require('./package.json').version")"
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ echo "Releasing @ionic/migrate@$version to the '${{ inputs.tag }}' tag"
+ shell: bash
+ - name: π§ Ensure Version Is Unpublished
+ run: |
+ set -euo pipefail
+ version="${{ steps.resolve-version.outputs.version }}"
+ # `npm view` exits non-zero both for an unpublished version and for a
+ # package that does not exist yet, and the latter is the expected state
+ # on the very first release. So inspect stdout rather than the exit
+ # code: empty output means the version is free to publish.
+ if [ -n "$(npm view "@ionic/migrate@$version" version 2>/dev/null || true)" ]; then
+ echo "::error::@ionic/migrate@$version is already published. Bump the version in packages/migrate/package.json."
+ exit 1
+ fi
+ shell: bash
+ - name: π Lint
+ run: npm run lint
+ shell: bash
+ - name: π§ͺ Test
+ run: npm test
+ shell: bash
+ - name: ποΈ Run Build
+ run: npm run build
+ shell: bash
+ # `--access public` is not passed here because publishConfig.access in
+ # package.json already sets it; a scoped package would otherwise default to
+ # restricted on its first publish.
+ - name: π Publish to NPM
+ run: npm publish --tag ${{ inputs.tag }} --provenance
+ shell: bash
diff --git a/.github/workflows/release-orchestrator.yml b/.github/workflows/release-orchestrator.yml
new file mode 100644
index 00000000000..67964f340d5
--- /dev/null
+++ b/.github/workflows/release-orchestrator.yml
@@ -0,0 +1,92 @@
+name: 'Release - Ionic Framework'
+
+on:
+ schedule:
+ # Run every Monday-Friday
+ # at 6:00 UTC (6:00 am UTC)
+ - cron: '00 06 * * 1-5'
+ workflow_dispatch:
+ inputs:
+ release-type:
+ description: 'Which Ionic release workflow should run?'
+ required: true
+ type: choice
+ default: dev
+ options:
+ - dev
+ - production
+ - migrate
+ version:
+ description: 'Which version should be published? (Only for production releases)'
+ required: false
+ type: choice
+ options:
+ - patch
+ - minor
+ - major
+ - prepatch
+ - preminor
+ - premajor
+ - prerelease
+ tag:
+ description: 'Which npm tag should this be published to? (Only for production and migrate releases)'
+ required: false
+ type: choice
+ default: latest
+ options:
+ - latest
+ - next
+ preid:
+ description: 'Which prerelease identifier should be used? (Only for production releases)'
+ required: false
+ type: choice
+ default: ''
+ options:
+ - ''
+ - alpha
+ - beta
+ - rc
+ - next
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ run-nightly:
+ if: ${{ github.event_name == 'schedule' }}
+ permissions:
+ contents: read
+ id-token: write
+ uses: ./.github/workflows/nightly.yml
+ secrets: inherit
+
+ run-dev:
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.release-type == 'dev' }}
+ permissions:
+ contents: read
+ id-token: write
+ uses: ./.github/workflows/dev-build.yml
+ secrets: inherit
+
+ run-migrate:
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.release-type == 'migrate' }}
+ permissions:
+ contents: read
+ id-token: write
+ uses: ./.github/workflows/release-migrate.yml
+ secrets: inherit
+ with:
+ tag: ${{ inputs.tag }}
+
+ run-production:
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.release-type == 'production' }}
+ permissions:
+ contents: write
+ id-token: write
+ uses: ./.github/workflows/release.yml
+ secrets: inherit
+ with:
+ version: ${{ inputs.version }}
+ tag: ${{ inputs.tag }}
+ preid: ${{ inputs.preid }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000000..bb552039012
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,148 @@
+name: 'Ionic Production Release'
+
+on:
+ workflow_call:
+ inputs:
+ version:
+ description: 'Which version should be published?'
+ required: true
+ type: string
+ tag:
+ description: 'Which npm tag should this be published to?'
+ required: true
+ type: string
+ preid:
+ description: 'Which prerelease identifier should be used? This is only needed when version is "prepatch", "preminor", "premajor", or "prerelease".'
+ required: false
+ type: string
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ validate_version:
+ name: β Validate Version Input
+ runs-on: ubuntu-latest
+ steps:
+ - name: π Ensure version is allowed
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ case "$VERSION" in
+ patch|minor|major|prepatch|preminor|premajor|prerelease)
+ exit 0
+ ;;
+ *)
+ echo "::error::Invalid version input: '$VERSION'. Allowed values: patch, minor, major, prepatch, preminor, premajor, prerelease."
+ exit 1
+ ;;
+ esac
+ shell: bash
+
+ release-ionic:
+ needs: [validate_version]
+ permissions:
+ contents: read
+ id-token: write
+ uses: ./.github/workflows/release-ionic.yml
+ with:
+ tag: ${{ inputs.tag }}
+ version: ${{ inputs.version }}
+ preid: ${{ inputs.preid }}
+
+ finalize-release:
+ needs: [release-ionic]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ id-token: write
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ token: ${{ secrets.IONITRON_TOKEN }}
+ fetch-depth: 0
+ - name: Configure Identity
+ # Commits from github-actions do not
+ # trigger other GitHub Actions. As a result,
+ # we publish releases from Ionitron instead
+ # so actions run when merging the release branch
+ # back into main.
+ run: |
+ git config user.name ionitron
+ git config user.email hi@ionicframework.com
+ shell: bash
+ - name: Create GitHub Release
+ run: lerna version ${{ inputs.version }} --yes --force-publish='*' --conventional-commits --create-release github --preid=${{ inputs.preid }}
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: bash
+
+ update-package-lock:
+ # This needs to run after finalize-release
+ # because we also push to the repo in that
+ # job. If these jobs ran in parallel then it is
+ # possible for them to push at the same time.
+ needs: [finalize-release]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ id-token: write
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ # Pull the latest version of the reference
+ # branch instead of the revision that triggered
+ # the workflow otherwise we won't get the commit
+ # created in the previous job and this next job
+ # will fail.
+ with:
+ ref: ${{ github.ref }}
+ - name: Configure Identity
+ # Commits from github-actions do not
+ # trigger other GitHub Actions. As a result,
+ # we push from Ionitron instead so actions
+ # run when merging the release branch
+ # back into main.
+ run: |
+ git config user.name ionitron
+ git config user.email hi@ionicframework.com
+ shell: bash
+ # Lerna does not automatically bump versions
+ # of Ionic dependencies that have changed,
+ # so we do that here.
+ - name: Bump Package Lock
+ run: |
+ lerna exec "npm install --package-lock-only"
+ git add .
+ git commit -m "chore(): update package lock files"
+ git push
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: bash
+
+ purge-cdn-cache:
+ needs: [release-ionic]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Purge JSDelivr Cache
+ run: |
+ curl -X POST \
+ https://purge.jsdelivr.net/ \
+ -H 'cache-control: no-cache' \
+ -H 'content-type: application/json' \
+ -d '{
+ "path": [
+ "/npm/@ionic/core@6/dist/ionic/ionic.esm.js",
+ "/npm/@ionic/core@7/dist/ionic/ionic.esm.js",
+ "/npm/@ionic/core@8/dist/ionic/ionic.esm.js",
+ "/npm/@ionic/core@9/dist/ionic/ionic.esm.js",
+ "/npm/@ionic/core@latest/dist/ionic/ionic.esm.js",
+ "/npm/@ionic/core@next/dist/ionic/ionic.esm.js",
+ "/npm/@ionic/core@6/css/ionic.bundle.css",
+ "/npm/@ionic/core@7/css/ionic.bundle.css",
+ "/npm/@ionic/core@8/css/ionic.bundle.css",
+ "/npm/@ionic/core@9/css/ionic.bundle.css",
+ "/npm/@ionic/core@latest/css/ionic.bundle.css"
+ "/npm/@ionic/core@next/css/ionic.bundle.css"
+ ]}'
+ shell: bash
diff --git a/.github/workflows/stencil-nightly.yml b/.github/workflows/stencil-nightly.yml
new file mode 100644
index 00000000000..5953e974d15
--- /dev/null
+++ b/.github/workflows/stencil-nightly.yml
@@ -0,0 +1,270 @@
+# This workflow is intended to run against the `HEAD` of Stencil's primary branch.
+# See https://github.com/ionic-team/stencil for contents of the repository
+name: 'Stencil Nightly Build'
+
+on:
+ schedule:
+ # Run every Monday-Friday
+ # at 6:00 UTC (6:00 am UTC)
+ - cron: '00 06 * * 1-5'
+ workflow_dispatch:
+ inputs:
+ npm_release_tag_core:
+ required: true
+ type: string
+ description: What stencil/core version should be pulled from NPM?
+ default: nightly
+ npm_release_tag_react_output_target:
+ required: true
+ type: string
+ description: What stencil/react-output-target version should be pulled from NPM?
+ default: latest
+
+# When pushing a new commit we should
+# cancel the previous test run to not
+# consume more runners than we need to.
+concurrency:
+ group: ${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ build-core-with-stencil-nightly:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-core-stencil-prerelease
+ with:
+ stencil-core-version: ${{ inputs.npm_release_tag_core || 'nightly' }}
+ stencil-react-output-target-version: ${{ inputs.npm_release_tag_react_output_target || 'latest' }}
+
+
+ test-core-clean-build:
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-clean-build
+
+ test-core-lint:
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-lint
+
+ test-core-spec:
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-spec
+ with:
+ stencil-version: ${{ inputs.npm_release_tag_core || 'nightly' }}
+
+ test-core-screenshot:
+ strategy:
+ # This ensures that all screenshot shard
+ # failures are reported so the dev can
+ # review everything at once.
+ fail-fast: false
+ matrix:
+ # Divide the tests into n buckets
+ # and run those buckets in parallel.
+ # To increase the number of shards,
+ # add new items to the shard array
+ # and change the value of totalShards
+ # to be the length of the shard array.
+ shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
+ totalShards: [20]
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-screenshot
+ with:
+ shard: ${{ matrix.shard }}
+ totalShards: ${{ matrix.totalShards }}
+
+ # Screenshots are required to pass
+ # in order for the branch to be merge
+ # eligible. However, the screenshot tests
+ # are run on n runners where n can change
+ # over time. The verify-screenshots step allows
+ # us to have a required status check for screenshot
+ # results without having to manually add each
+ # matrix run in the branch protection rules
+ # Source: https://github.community/t/status-check-for-a-matrix-jobs/127354
+ verify-screenshots:
+ if: ${{ always() }}
+ needs: test-core-screenshot
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-core-screenshot.result != 'success' }}
+ run: exit 1
+
+ build-vue:
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-vue
+
+ build-vue-router:
+ needs: [build-vue]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-vue-router
+
+ test-vue-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [vue3]
+ needs: [build-vue, build-vue-router]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-vue-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-vue-e2e:
+ if: ${{ always() }}
+ needs: test-vue-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-vue-e2e.result != 'success' }}
+ run: exit 1
+
+ build-angular:
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-angular
+
+ build-angular-server:
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-angular-server
+
+ test-angular-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [ng18, ng19, ng20, ng21, ng22]
+ needs: [build-angular, build-angular-server]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-angular-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-angular-e2e:
+ if: ${{ always() }}
+ needs: test-angular-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-angular-e2e.result != 'success' }}
+ run: exit 1
+
+ build-react:
+ needs: [build-core-with-stencil-nightly]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - name: Install Stencil React Output Target
+ working-directory: ./packages/react
+ run: npm i @stencil/react-output-target@${{ inputs.npm_release_tag_react_output_target || 'latest' }}
+ shell: bash
+ - uses: ./.github/workflows/actions/build-react
+
+ build-react-router:
+ needs: [build-react]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-react-router
+
+ test-react-router-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [reactrouter6-react18, reactrouter6-react19]
+ needs: [build-react, build-react-router]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-react-router-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-react-router-e2e:
+ if: ${{ always() }}
+ needs: test-react-router-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-react-router-e2e.result != 'success' }}
+ run: exit 1
+
+ test-react-e2e:
+ strategy:
+ fail-fast: false
+ matrix:
+ apps: [react18, react19]
+ needs: [build-react, build-react-router]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-react-e2e
+ with:
+ app: ${{ matrix.apps }}
+
+ verify-test-react-e2e:
+ if: ${{ always() }}
+ needs: test-react-e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check build matrix status
+ if: ${{ needs.test-react-e2e.result != 'success' }}
+ run: exit 1
+
+ send-success-messages:
+ needs: [test-core-clean-build, test-core-lint, test-core-spec, verify-screenshots, verify-test-vue-e2e, verify-test-angular-e2e, verify-test-react-router-e2e, verify-test-react-e2e]
+ runs-on: ubuntu-latest
+ if: ${{ !cancelled() && !contains(needs.*.result, 'failure') }}
+ steps:
+ - name: Notify success on Discord
+ run: |
+ curl -H "Content-Type:application/json" \
+ -d '{"embeds": [{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} finished successfully", "color": 65280, "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}]}' \
+ ${{secrets.DISCORD_NOTIFY_WEBHOOK}}
+ - name: Notify success on Slack
+ run: |
+ curl -H "Content-Type:application/json" \
+ -d '{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} finished successfully", "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}' \
+ ${{secrets.SLACK_NOTIFY_SUCCESS_WEBHOOK}}
+
+ send-failure-messages:
+ needs: [test-core-clean-build, test-core-lint, test-core-spec, verify-screenshots, verify-test-vue-e2e, verify-test-angular-e2e, verify-test-react-router-e2e, verify-test-react-e2e]
+ runs-on: ubuntu-latest
+ if: ${{ !cancelled() && contains(needs.*.result, 'failure') }}
+ steps:
+ - name: Notify failure on Discord
+ run: |
+ curl -H "Content-Type:application/json" \
+ -d '{"content": "Alerting <@&1347593178580254761>!", "embeds": [{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} failed", "color": 16711680, "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}]}' \
+ ${{secrets.DISCORD_NOTIFY_WEBHOOK}}
+ - name: Notify failure on Slack
+ run: |
+ curl -H "Content-Type:application/json" \
+ -d '{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} failed", "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}' \
+ ${{secrets.SLACK_NOTIFY_FAILURE_WEBHOOK}}
diff --git a/.github/workflows/update-screenshots.yml b/.github/workflows/update-screenshots.yml
new file mode 100644
index 00000000000..acfe30ef553
--- /dev/null
+++ b/.github/workflows/update-screenshots.yml
@@ -0,0 +1,72 @@
+name: 'Update Reference Screenshots'
+
+on:
+ workflow_dispatch:
+ inputs:
+ # Screenshots can be updated for all components or specified component(s).
+ # If the `component` variable is set, then the test has the option to
+ # - run all the instances of the specified component(s) in the `src/components` folder
+ # -- For example: if the `component` value is "item", then the following command will be: `npm run test.e2e item`
+ # - run the specified file path
+ # -- For example: if the `component` value is "src/components/item/test/basic", then the following command will be: `npm run test.e2e src/components/item/test/basic`
+ # - run multiple specified components based on the space-separated value
+ # -- For example: if the `component` value is "item basic", then the following command will be: `npm run test.e2e item basic`
+ # -- For example: if the `component` value is "src/components/item/test/basic src/components/item/test/a11y", then the following command will be: `npm run test.e2e src/components/item/test/basic src/components/item/test/a11y`
+ #
+ # If the `component` variable is not set, then the test will run all the instances of the components in the `src/components` folder.
+ # - For example: `npm run test.e2e`
+ #
+ # More common options can be found at the Playwright Command line page: https://playwright.dev/docs/test-cli
+ component:
+ description: 'What component(s) should be updated? (leave blank to update all or use a space-separated list for multiple components)'
+ required: false
+ default: ''
+
+jobs:
+ build-core:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/build-core
+
+ test-core-screenshot:
+ strategy:
+ # This ensures that all screenshot shard
+ # failures are reported so the dev can
+ # review everything at once.
+ fail-fast: false
+ matrix:
+ # Divide the tests into n buckets
+ # and run those buckets in parallel.
+ # To increase the number of shards,
+ # add new items to the shard array
+ # and change the value of totalShards
+ # to be the length of the shard array.
+ shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
+ totalShards: [20]
+ needs: [build-core]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/workflows/actions/test-core-screenshot
+ with:
+ shard: ${{ matrix.shard }}
+ totalShards: ${{ matrix.totalShards }}
+ update: true
+ component: ${{ inputs.component }}
+
+ update-reference-screenshots:
+ runs-on: ubuntu-latest
+ needs: [test-core-screenshot]
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ # Normally, we could just push with the
+ # default GITHUB_TOKEN, but that will
+ # not cause the build workflow
+ # to re-run. We use Ionitron's
+ # Personal Access Token instead
+ # to allow for this build workflow
+ # to run when the screenshots are pushed.
+ with:
+ token: ${{ secrets.IONITRON_TOKEN }}
+ - uses: ./.github/workflows/actions/update-reference-screenshots
diff --git a/.gitignore b/.gitignore
index 3aed639190d..d49be2c644e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,34 @@
*~
*.sw[mnpcod]
*.log
+*.lock
*.tmp
*.tmp.*
log.txt
*.sublime-project
*.sublime-workspace
+*.tgz
.idea/
.vscode/
.sass-cache/
.versions/
coverage/
+collection/
dist/
+dist-transpiled/
node_modules/
tmp/
temp/
+core/theme-builder/
+core/test-components/
+core/css/
$RECYCLE.BIN/
.DS_Store
Thumbs.db
UserInterfaceState.xcuserstate
+.env
.package.tmp.json
@@ -29,13 +37,61 @@ scripts/e2e/webpackEntryPoints.json
scripts/build/e2e-generated-tsconfig.json
*.css.ts
+stats.json
+
# demo stuff
demos/node_modules
demos/polyfills
demos/css
demos/fonts
-demos/src/**/*.js*
+demos/src/**/*.js
+demos/src/**/*.map
demos/src/**/*.ngfactory.ts
demos/src/**/*.d.ts
demos/src/**/*.metadata.json
demos/src/**/*.css.shim.ts
+prerender.html
+prerender-domino.html
+prerender-hydrated.html
+prerender-static.html
+
+# stencil
+packages/react/css/
+packages/vue/css/
+core/components/
+core/css/
+core/hydrate/
+core/loader/
+core/www/
+.stencil/
+
+# playwright
+core/test-results/
+core/playwright-report/
+packages/angular/test-results/
+
+# ground truths generated outside of docker should not be committed to the repo
+core/**/*-snapshots/*
+
+# new ground truths should only be generated inside of docker which will result in -linux.png screenshots
+!core/**/*-snapshots/*-linux.png
+
+# these files are going to be different per-developer environment so do not add them to the repo
+core/docker-display.txt
+core/docker-display-volume.txt
+
+# angular
+packages/angular/css/
+packages/angular/test/build/
+.angular/
+
+# vue
+packages/vue/test/build/
+
+# react
+packages/react/test/build/
+
+# react router
+packages/react-router/test/build/
+
+.npmrc
diff --git a/.scripts/bump-version.js b/.scripts/bump-version.js
new file mode 100644
index 00000000000..4ef17c089f8
--- /dev/null
+++ b/.scripts/bump-version.js
@@ -0,0 +1,10 @@
+const semver = require('semver');
+
+const getDevVersion = () => {
+ const originalVersion = require('../lerna.json').version;
+ const baseVersion = semver.inc(originalVersion, 'patch');
+
+ return baseVersion;
+}
+
+console.log(getDevVersion());
diff --git a/.scss-lint.yml b/.scss-lint.yml
deleted file mode 100644
index 8f47412b545..00000000000
--- a/.scss-lint.yml
+++ /dev/null
@@ -1,209 +0,0 @@
-# Default application configuration that all configurations inherit from.
-
-# See config at https://github.com/brigade/scss-lint/blob/master/config/default.yml
-
-exclude:
- - 'src/components/item/item.ios.scss'
- - 'src/components/item/item.md.scss'
- - 'src/components/list/list.ios.scss'
- - 'src/components/show-hide-when/**'
- - 'src/components/slides/**'
- - 'src/themes/ionic.mixins.scss'
- - 'src/themes/license.scss'
- - 'src/themes/util.scss'
- - 'src/themes/version.scss'
- - 'src/platform/cordova.*.scss'
-
-
-linters:
- ColorVariable:
- enabled: false
-
- DuplicateProperty:
- enabled: false
-
- MergeableSelector:
- enabled: true
- force_nesting: false
-
- PropertySortOrder:
- enabled: true
- ignore_unspecified: false
- min_properties: 2
- separate_groups: true
- order:
-
- # Box
-
- - position
- - top
- - right
- - bottom
- - left
- - z-index
- - display
- - overflow
- - float
- - clear
- -
- - flex
- - flex-basis
- - flex-direction
- - flex-flow
- - flex-grow
- - flex-shrink
- - flex-wrap
- - align-content
- - align-items
- - align-self
- - justify-content
- - order
- -
- - margin
- - margin-top
- - margin-right
- - margin-bottom
- - margin-left
- - padding
- - padding-top
- - padding-right
- - padding-bottom
- - padding-left
- -
- - width
- - min-width
- - max-width
- - height
- - min-height
- - max-height
- -
- - columns
- - column-gap
- - column-fill
- - column-rule
- - column-span
- - column-count
- - column-width
-
- # Border
-
- - border
- - border-top
- - border-right
- - border-bottom
- - border-left
- - border-width
- - border-top-width
- - border-right-width
- - border-bottom-width
- - border-left-width
-
- - border-style
- - border-top-style
- - border-right-style
- - border-bottom-style
- - border-left-style
-
- - border-radius
- - border-top-left-radius
- - border-top-right-radius
- - border-bottom-left-radius
- - border-bottom-right-radius
-
- - border-color
- - border-top-color
- - border-right-color
- - border-bottom-color
- - border-left-color
-
- - outline
- - outline-color
- - outline-offset
- - outline-style
- - outline-width
-
- # Text
-
- - font
- - font-family
- - font-size
- - font-smoothing
- - font-style
- - font-variant
- - font-weight
-
- - letter-spacing
- - line-height
- - list-style
- - text-align
- - text-decoration
- - text-indent
- - text-overflow
- - text-rendering
- - text-shadow
- - text-transform
- - text-wrap
-
- - white-space
- - word-spacing
-
- - color
-
- # Background
-
- - background
- - background-attachment
- - background-color
- - background-image
- - background-repeat
- - background-position
- - background-size
-
- # Other
-
- - border-collapse
- - border-spacing
- - box-shadow
- - caption-side
- - content
- - cursor
- - empty-cells
- - opacity
- - quotes
- - speak
- - table-layout
- - vertical-align
- - visibility
-
- # Transforms
-
- - transform
- - transform-box
- - transform-origin
- - transform-style
-
- - transition
- - transition-delay
- - transition-duration
- - transition-property
- - transition-timing-function
-
- # This doesn't allow chaining between elements and attributes, ids or classes
- # if we turn this on it breaks a lot of components
- QualifyingElement:
- enabled: false
- allow_element_with_attribute: false
- allow_element_with_class: false
- allow_element_with_id: false
-
- SelectorFormat:
- enabled: false
- convention: hyphenated_lowercase # or 'strict_BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
-
- StringQuotes:
- enabled: true
- style: double_quotes
-
- PropertySpelling:
- extra_properties:
- - contain
diff --git a/.stackblitz/codeflow.json b/.stackblitz/codeflow.json
new file mode 100644
index 00000000000..6354218ee30
--- /dev/null
+++ b/.stackblitz/codeflow.json
@@ -0,0 +1,10 @@
+{
+ "bot": {
+ "issues": {
+ "enabled": false
+ },
+ "pullRequests": {
+ "enabled": false
+ }
+ }
+}
diff --git a/.vercelignore b/.vercelignore
new file mode 100644
index 00000000000..c18553dfa1e
--- /dev/null
+++ b/.vercelignore
@@ -0,0 +1 @@
+core/src/components/**/*/*.png
diff --git a/BREAKING.md b/BREAKING.md
new file mode 100644
index 00000000000..2ca32ea114b
--- /dev/null
+++ b/BREAKING.md
@@ -0,0 +1,712 @@
+# Breaking Changes
+
+This is a comprehensive list of the breaking changes introduced in the major version releases of Ionic Framework.
+
+## Versions
+
+- [Version 9.x](#version-9x)
+- [Version 8.x](./BREAKING_ARCHIVE/v8.md)
+- [Version 7.x](./BREAKING_ARCHIVE/v7.md)
+- [Version 6.x](./BREAKING_ARCHIVE/v6.md)
+- [Version 5.x](./BREAKING_ARCHIVE/v5.md)
+- [Version 4.x](./BREAKING_ARCHIVE/v4.md)
+- [Legacy](https://github.com/ionic-team/ionic-v3/blob/master/CHANGELOG.md)
+
+## Version 9.x
+
+- [Browser and Platform Support](#version-9x-browser-platform-support)
+- [Package Exports](#version-9x-package-exports)
+- [Components](#version-9x-components)
+ - [Input](#version-9x-input)
+ - [Legacy Picker](#version-9x-legacy-picker)
+ - [Modal](#version-9x-modal)
+ - [Nav](#version-9x-nav)
+ - [Router Outlet](#version-9x-router-outlet)
+ - [Searchbar](#version-9x-searchbar)
+ - [Select](#version-9x-select)
+ - [Textarea](#version-9x-textarea)
+- [Framework Specific](#version-9x-framework-specific)
+ - [Angular](#version-9x-angular)
+ - [React](#version-9x-react)
+ - [Vue](#version-9x-vue)
+
+
Browser and Platform Support
+
+This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 9.
+
+**Minimum Browser Versions**
+| Desktop Browser | Supported Versions |
+| --------------- | ------------------ |
+| Chrome | 89+ |
+| Safari | 16+ |
+| Edge | 89+ |
+| Firefox | 75+ |
+
+**Minimum JavaScript Framework Versions**
+| Framework | Supported Version |
+| --------- | --------------------- |
+| Angular | 18+ |
+| React | 18 or 19 |
+| Vue | 3.5+ |
+
+**Minimum Mobile Platform Versions**
+| Platform | Supported Version |
+| -------- | ---------------------- |
+| iOS | 16+ |
+| Android | 5.1+ with Chromium 89+ |
+
+**Minimum Native Runtime Versions**
+| Native Runtime | Supported Version |
+| -------------- | ----------------- |
+| Capacitor | 7+ |
+
+Ionic's native platform detection no longer checks the Capacitor 2 `isNative` flag. `isCapacitorNative` now relies solely on `Capacitor.isNativePlatform()`, which was added in Capacitor 3. Apps running Capacitor 2 will no longer be detected as a native/hybrid platform, so `isPlatform('capacitor')`, `isPlatform('hybrid')`, and `getPlatforms()` will report web instead of native. Upgrade to a supported Capacitor version (7 or later).
+
+
Package Exports
+
+`@ionic/core`'s `package.json` now declares an `exports` field. Subpaths like `@ionic/core/components` and `@ionic/core/loader` previously failed under Node ESM (Angular 21's default Vitest builder, raw Node, etc.) with `ERR_UNSUPPORTED_DIR_IMPORT`, because the strict ESM resolver doesn't read the nested `package.json` files this package relied on. The new `exports` map declares the documented subpaths explicitly.
+
+`exports` is an allowlist. Apps using Node ESM, webpack 5, or TypeScript `moduleResolution: "bundler"`/`"node16"`/`"nodenext"` that import from undocumented internal paths need to switch to one of the supported subpaths:
+
+| Subpath | Use |
+| ---------------------------------- | ----------------------------------------------------- |
+| `@ionic/core` | Root entry, controllers, animation builders |
+| `@ionic/core/components` | Custom-element constructors and shared utilities |
+| `@ionic/core/components/ion-*.js` | Single-component custom-element constructor |
+| `@ionic/core/loader` | `defineCustomElements` lazy loader |
+| `@ionic/core/hydrate` | SSR hydration entry |
+| `@ionic/core/css/*.css` | Global stylesheets and palettes |
+
+Apps on `moduleResolution: "node"` (classic) and webpack 4 keep resolving through the legacy fields and are unaffected.
+
+
Components
+
+
Input
+
+**`autocorrect` Property Type Changed to Boolean**
+
+The `autocorrect` property on `ion-input` is now a `boolean` and defaults to `false`. It was previously typed as `'on' | 'off'` with a default of `'off'`. This resolves a type conflict introduced when TypeScript 5.9 added `autocorrect: boolean` to the DOM `HTMLElement` interface.
+
+The string form no longer behaves the same way. Because an HTML attribute coerces to `true` for any non-empty string, `autocorrect="off"` now evaluates to `true` (autocorrect enabled). Migrate to the boolean property:
+
+- Remove the attribute to keep autocorrect disabled (the default).
+- Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue).
+
+**Floating Label Behavior**
+
+Floating labels no longer automatically float when the input contains slotted content. Labels float only when the input is focused or has a value.
+
+**Internal DOM Structure Changes**
+
+The internal DOM structure has been reorganized to support floating labels with slotted content.
+
+Added:
+- `.input-start`
+- `.input-control`
+- `.input-end`
+
+Restructured:
+- `.label-text-wrapper` moved from `.input-wrapper` into `.input-control`
+- `.native-wrapper` moved from `.input-wrapper` into `.input-control`
+- Start slot moved from `.native-wrapper` into `.input-start`
+- Clear button icon moved from `.native-wrapper` into `.input-end`
+- End slot moved from `.native-wrapper` into `.input-end`
+- `.input-control` now contains the label text and native `input`, while start/end content is separated into dedicated wrappers
+
+Update your selectors to account for these structural changes:
+
+```diff
+-ion-input .input-wrapper .native-wrapper { }
++ion-input .input-control .native-wrapper { }
+
+-ion-input .input-wrapper .native-wrapper [slot="start"] { }
++ion-input .input-start [slot="start"] { }
+
+-ion-input .input-wrapper .native-wrapper .input-clear-icon { }
++ion-input .input-end .input-clear-icon { }
+
+-ion-input .input-wrapper .native-wrapper [slot="end"] { }
++ion-input .input-end [slot="end"] { }
+```
+
+
Legacy Picker
+
+- `ion-picker-legacy` and `ion-picker-legacy-column` have been removed. The legacy picker component has been replaced with an inline picker component.
+ - Usages such as `ion-picker-legacy` or `IonPickerLegacy` should be changed to `ion-picker` and `IonPicker`, respectively.
+- Remove any usages of `pickerController`. If using React, remove any usages of the `useIonPicker` hook. These controller-based APIs have been removed. Use the inline picker component instead.
+- Remove any usages of the `PickerOptions`, `PickerButton`, `PickerColumn`, and `PickerColumnOption` type exports. These types were associated with the legacy picker and have been removed.
+
+
Modal
+
+The `handleBehavior` property on `ion-modal` now defaults to `"cycle"` instead of `"none"`. For sheet modals that display a handle, this means the handle is now focusable and activating it (by click, keyboard, or screen reader) cycles the sheet through its available breakpoints. This matches the native iOS sheet behavior and keeps sheet modals operable for assistive technology users by default.
+
+Sheet modals that relied on the handle being inert should set `handleBehavior="none"` to restore the previous behavior:
+
+```html
+
+```
+
+
Nav
+
+`ion-nav` no longer integrates with `ion-router`. It is now a standalone imperative stack navigation component, driven only through its own API (`root`, `push`, `pop`, `setRoot`, etc.) and `ion-nav-link`.
+
+The following behaviors have been removed:
+
+- The router no longer discovers or drives an `ion-nav`. Placing an `ion-nav` inside an `ion-router` no longer turns it into a routed outlet.
+- Navigating an `ion-nav` (via `push`, `pop`, `ion-nav-link`, or the swipe-to-go-back gesture) no longer updates the URL, and the router's navigation guards no longer run for `ion-nav` transitions.
+- The internal `setRouteId()` and `getRouteId()` methods and the `updateURL` nav option have been removed.
+
+Apps that relied on `ion-nav` to update the URL (for example, pushing components and expecting the browser URL to change) should use `ion-router-outlet` for URL-based routing. Keep the `ion-route` definitions and swap the outlet element:
+
+```diff
+
+
+
+
+
+-
++
+```
+
+An `ion-nav` can still be used inside a routed page for local, URL-less stack navigation. It manages its own stack via `root` and `ion-nav-link`, and the URL never changes as you push and pop:
+
+```html
+
+
+
+
+```
+
+
Router Outlet
+
+`ion-router-outlet` now exposes a `swipeGesture` property that controls the swipe-to-go-back gesture per outlet. This property defaults to `true` in `"ios"` mode and `false` in `"md"` mode.
+
+**`swipeBackEnabled` Config Behavior Change**
+
+In React and Vue, the `swipeBackEnabled` config option is now read once when the outlet mounts. Apps that dynamically toggle this config value at runtime should migrate to the `swipeGesture` property instead:
+
+**React:**
+
+```diff
+- setupIonicReact({ swipeBackEnabled: someCondition });
++
+```
+
+**Vue:**
+
+```diff
+- createApp(App).use(IonicVue, { swipeBackEnabled: someCondition })
++
+```
+
+**Disabling Swipe-to-Go-Back**
+
+To disable the gesture on a specific outlet, set `swipeGesture` to `false`:
+
+```tsx
+
+```
+
+The `swipeBackEnabled` config option is still respected as the initial default and does not need to change for apps that set it once at startup.
+
+
Searchbar
+
+The `autocorrect` property on `ion-searchbar` is now a `boolean` and defaults to `false`. It was previously typed as `'on' | 'off'` with a default of `'off'`. This resolves a type conflict introduced when TypeScript 5.9 added `autocorrect: boolean` to the DOM `HTMLElement` interface.
+
+The string form no longer behaves the same way. Because an HTML attribute coerces to `true` for any non-empty string, `autocorrect="off"` now evaluates to `true` (autocorrect enabled). Migrate to the boolean property:
+
+- Remove the attribute to keep autocorrect disabled (the default).
+- Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue).
+
+
Select
+
+**`ionChange` Only Fires When the Value Changes**
+
+The `ionChange` event on `ion-select` now only fires when the selected value actually changes. Previously, the `alert` and `action-sheet` interfaces emitted `ionChange` every time the overlay was confirmed, even when the user chose the option that was already selected. This aligns the `alert` and `action-sheet` interfaces with the existing behavior of the `popover` and `modal` interfaces, and with the documented contract of `ionChange`.
+
+Apps that relied on `ionChange` firing on every confirmation (for example, to detect overlay dismissal without a value change) should listen for `ionDismiss` instead, or use the `didDismiss` event on the underlying alert or action sheet.
+
+**Action Sheet Interface `selected` Role Removed**
+
+When using `interface="action-sheet"`, `ion-select` no longer assigns the `selected` role to the action sheet button for the currently selected option. This aligns the `action-sheet` interface with the `alert`, `popover`, and `modal` interfaces, none of which assign this role. This does not change the selected option's styling.
+
+Previously, the `selected` role was assigned only to the option matching the select's current value. Because the dismiss role mirrors the tapped button, this surfaced in just one case: re-selecting the already-selected option dismissed the action sheet with `role: "selected"` in `ionActionSheetDidDismiss`. Tapping any other option changed the value and dismissed with `role: ""`. Now that the role is no longer assigned, both cases dismiss with `role: undefined`. Apps that inspected this role to detect that a value was chosen, such as reading `role` from the underlying action sheet's `onDidDismiss` result, should listen for `ion-select`'s `ionChange` event instead, which emits the selected value when the selection changes.
+
+**Floating Label Behavior**
+
+Floating labels no longer automatically float when the select contains slotted content. Labels float only when the select is focused or has a value. Additionally, when using a floating label, the placeholder is only visible when the select is focused.
+
+**Internal DOM Structure Changes**
+
+The internal DOM structure has been reorganized to support floating labels with slotted content. This changes the structure and location of several exposed shadow parts.
+
+Added:
+- `.select-start` β `part="start"`
+- `.select-control` β `part="control"`
+- `.select-end` β `part="end"`
+
+Removed:
+- `.select-wrapper-inner` β `part="inner"`
+
+Restructured:
+- `.label-text-wrapper` remains `part="label"` but moved from `.select-wrapper` into `.select-control`
+- `.native-wrapper` remains `part="container"` but moved from `.select-wrapper-inner` into `.select-control`
+- Start slot moved from `.select-wrapper-inner` into `.select-start` (`part="start"`)
+- End slot moved from `.select-wrapper-inner` into `.select-end` (`part="end"`)
+- `.select-icon` remains `part="icon"` but its location depends on the label state:
+ - With a start/end label, the icon is inside `.native-wrapper`
+ - With a floating/stacked label, the icon is inside `.select-end`
+
+Update selectors that target the exposed shadow parts to account for the new structure:
+
+If you currently target `part="inner"`, that part has been removed. Update those styles to target the new parts as appropriate.
+
+If you target `part="label"`, `part="container"`, or `part="icon"`, the part names remain unchanged, but their position in the shadow DOM has changed. This may affect styles that depend on the relationship or layout of these parts.
+
+Use the new `part="start"`, `part="control"`, and `part="end"` parts to target the new structural wrappers.
+
+
Textarea
+
+**Floating Label Behavior**
+
+Floating labels no longer automatically float when the textarea contains slotted content. Labels float only when the textarea is focused or has a value.
+
+**Internal DOM Structure Changes**
+
+The internal DOM structure has been reorganized to support floating labels with slotted content.
+
+Removed: `.textarea-wrapper-inner`
+
+Added: `.textarea-control`
+
+Renamed:
+- `.start-slot-wrapper` β `.textarea-start`
+- `.end-slot-wrapper` β `.textarea-end`
+
+Restructured:
+- `.label-text-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control`
+- `.native-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control`
+- `.start-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-start`
+- `.end-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-end`
+
+Update your selectors to account for these structural changes:
+
+```diff
+-ion-textarea .textarea-wrapper-inner .native-wrapper { }
++ion-textarea .textarea-control .native-wrapper { }
+
+-ion-textarea .start-slot-wrapper [slot="start"] { }
++ion-textarea .textarea-start [slot="start"] { }
+
+-ion-textarea .end-slot-wrapper [slot="end"] { }
++ion-textarea .textarea-end [slot="end"] { }
+```
+
+**Minimum Height Change**
+
+The minimum height of textarea in Material Design (`md` mode) is now `72px`. At the default number of rows this makes textareas the same height regardless of the `fill` property or `labelPlacement`. Previously the minimum height was:
+
+| Fill | Label placement | Previous minimum height |
+| --- | --- | --- |
+| default | `start`, `end`, `fixed` | `44px` |
+| default | `floating`, `stacked` | `56px` |
+| `solid`, `outline` | any | `56px` |
+
+These were minimums, not the heights textareas actually rendered at. A textarea with content in the `start` or `end` slots was already taller than its minimum, so the change affects it differently. For example, a `fill="solid"` textarea with slotted icons and buttons previously rendered at `72px` with a `start` label and `81px` with a `floating` label. Both are now `72px`, so that floating label case is `9px` shorter than before rather than taller.
+
+Because `72px` is taller than two rows of text, `rows` values below `3` no longer change the height of the textarea in `md` mode: `rows="1"` and `rows="2"` both render at `72px`.
+
+If you were relying on the previous heights, or you need `rows` to control the height, override the minimum height back. The override has to be more specific than the component's own style, so a bare `ion-textarea` selector will not apply. Add a custom class to the textarea to increase specificity:
+
+```css
+/* Add a custom class to the textarea */
+ion-textarea.custom {
+ min-height: 44px;
+}
+```
+
+
Framework Specific
+
+
Angular
+
+**Minimum Angular Version**
+
+Ionic 9 requires Angular 18 or later. Angular 16 and 17 are no longer supported.
+
+**Standalone Components Imported by Default**
+
+Following industry standards, Ionic 9 makes standalone components the default import path. Standalone component imports have changed from `@ionic/angular/standalone` to `@ionic/angular`. Lazy-loaded component imports have changed from `@ionic/angular` to `@ionic/angular/lazy`.
+
+**IonicModule Deprecation**
+
+`IonicModule` is deprecated in Ionic 9 and will be removed in a future major version. It remains fully functional in Ionic 9, so existing applications continue to work without changes.
+
+Applications should migrate to `provideIonicAngular()`, which works in both standalone and NgModule-based applications. For an NgModule-based app, replace `IonicModule.forRoot(config)` in the `imports` array with `provideIonicAngular(config)` in the `providers` array. Any config passed to `IonicModule.forRoot()` can be passed as an object to `provideIonicAngular()`. Refer to the [build options guide](https://ionicframework.com/docs/angular/build-options) for migration steps.
+
+**Zoneless Change Detection by Default**
+
+Ionic 9 defaults to zoneless change detection. Angular 21 bootstraps zoneless out of the box, so a new Ionic 9 app on Angular 21 runs without Zone.js and requires no change-detection provider. The `ng add @ionic/angular` schematic no longer registers `provideZoneChangeDetection()`.
+
+Because Zone.js no longer triggers change detection automatically, component state that you update from an asynchronous callback that Angular doesn't wrap (awaiting an overlay result such as `modal.onWillDismiss()`, `setTimeout`, RxJS subscriptions, `Platform` events) no longer re-renders on its own. Update a signal or call `ChangeDetectorRef.markForCheck()` in those callbacks. Template event bindings, `@HostListener`, reactive forms, and Ionic lifecycle hooks (`ionViewWillEnter`, etc.) that set state synchronously are unaffected. Refer to the [Zoneless Change Detection guide](https://ionicframework.com/docs/angular/zoneless) for the patterns.
+
+On Angular 18 through 20, Zone.js remains Angular's default, so those versions are unaffected and require no change. To adopt zoneless there, add `provideZonelessChangeDetection()` (named `provideExperimentalZonelessChangeDetection()` on Angular 18 and 19).
+
+**Keeping Zone.js on Angular 21 (optional)**
+
+To keep using Zone.js on Angular 21, opt back in with `provideZoneChangeDetection()` and keep `zone.js` in your polyfills.
+
+Standalone bootstrap:
+
+```diff
+ import { bootstrapApplication } from '@angular/platform-browser';
++ import { provideZoneChangeDetection } from '@angular/core';
+
+ bootstrapApplication(AppComponent, {
+ providers: [
++ provideZoneChangeDetection(),
+ // ...other providers
+ ],
+ });
+```
+
+NgModule bootstrap:
+
+```diff
+ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
++ import { provideZoneChangeDetection } from '@angular/core';
+
+ platformBrowserDynamic()
+- .bootstrapModule(AppModule)
++ .bootstrapModule(AppModule, {
++ applicationProviders: [provideZoneChangeDetection()],
++ })
+ .catch((err) => console.error(err));
+```
+
+Angular forbids `provideZoneChangeDetection()` inside an NgModule's `providers` array, so for NgModule apps it must be passed as `applicationProviders` on the `bootstrapModule()` call. Both paths also require `zone.js` in your polyfills, which Angular 21's default scaffold omits:
+
+```ts
+// src/polyfills.ts
+import 'zone.js';
+```
+
+**OnPush by Default on Angular 22**
+
+Angular 22 changes the default change detection strategy to `OnPush` for components that don't declare one. Combined with the zoneless default above, any component state that you mutate as a plain field from an Ionic lifecycle hook (`ionViewWillEnter`, etc.) no longer re-renders on its own. Run `ng update`, which migrates existing components to eager change detection and preserves the previous behavior, or use a signal (or `ChangeDetectorRef.markForCheck()`) for state set in those hooks. Ionic's own Angular components already declare `OnPush` explicitly and are unaffected. Angular 18 through 21 keep the eager default, so they require no change.
+
+**TypeScript**
+
+Ionic 9 supports TypeScript 5.4 or later, matching the minimum for Angular 18. Angular 21 requires TypeScript 5.9 or later, and Angular 22 requires TypeScript 6.0 or later, per Angular's own requirements.
+
+**Node.js**
+
+Angular 22 raises the minimum Node.js version to `^22.22.3 || ^24.15.0 || ^26.0.0`. Angular 18 through 21 are unaffected.
+
+**Module Resolution**
+
+`@ionic/angular` is now published with `exports`-based subpath resolution. Apps using TypeScript `moduleResolution: "node"` (classic) can fail to resolve subpaths such as `@ionic/angular/lazy`. Set `moduleResolution` to `"bundler"` (the default for `ng new` on Angular 17 and later). Refer to [Package Exports](#version-9x-package-exports).
+
+**CSS Imports No Longer Use the `~` Prefix**
+
+Angular's current build pipeline no longer supports the webpack-loader `~` prefix in CSS `@import` statements:
+
+```diff
+- @import '~@ionic/angular/css/core.css';
++ @import '@ionic/angular/css/core.css';
+```
+
+
React
+
+The `@ionic/react` and `@ionic/react-router` packages now require React 18 or 19. React 17 is no longer supported.
+
+The `@ionic/react-router` package now requires React Router v6. React Router v5 is no longer supported.
+
+**Minimum Version Requirements**
+| Package | Supported Version |
+| ---------------- | ----------------- |
+| react | 18 or 19 |
+| react-dom | 18 or 19 |
+| react-router | 6.4.0+ |
+| react-router-dom | 6.4.0+ |
+
+**TypeScript**
+
+The `@ionic/react` package now requires TypeScript 5.4 or later. Its type definitions use `NoInfer`, which TypeScript added in 5.4. This matches the minimum that `@ionic/angular` already requires.
+
+**Typed Overlay Hook Props**
+
+The `useIonModal` and `useIonPopover` hooks type `componentProps` against the component they are given, instead of accepting `any`. Props that do not match the component are a compile error, and `componentProps` is required when the component declares required props. Applications passing incorrect props will see new type errors at build time rather than failing at runtime.
+
+```diff
+ const Modal: React.FC<{ title: string }> = ({ title }) => {title};
+
+- const [present, dismiss] = useIonModal(Modal, { subtitle: 'Wrong' });
++ const [present, dismiss] = useIonModal(Modal, { title: 'Hello' });
+```
+
+Props are read from the component rather than from `componentProps`, so a component declared inline needs its props annotated:
+
+```diff
+- const [present, dismiss] = useIonModal(({ name }) =>
, { name: 'Dave' });
+```
+
+Passing a JSX element rather than a component is unchanged, and `componentProps` is not type checked in that case.
+
+`npx @ionic/migrate` reports the calls this affects and names what is wrong with each, but does not rewrite them, since the right fix depends on what the call was meant to do. For the inline case above, `--experimental` can annotate the parameter from the `componentProps` object literal being passed.
+
+React Router v6 introduces several API changes that will require updates to your application's routing configuration:
+
+**Route Definition Changes**
+
+The `component` prop has been replaced with the `element` prop, which accepts JSX:
+
+```diff
+-
++ } />
+```
+
+**Redirect Changes**
+
+The `` component has been replaced with ``:
+
+```diff
+- import { Redirect } from 'react-router-dom';
++ import { Navigate } from 'react-router-dom';
+
+-
++
+```
+
+**Nested Route Paths**
+
+Routes that contain nested routes or child `IonRouterOutlet` components need a `/*` suffix to match sub-paths:
+
+```diff
+- } />
++ } />
+```
+
+**Accessing Route Parameters**
+
+Route parameters are now accessed via the `useParams` hook instead of props:
+
+```diff
+- import { RouteComponentProps } from 'react-router-dom';
++ import { useParams } from 'react-router-dom';
+
+- const MyComponent: React.FC> = ({ match }) => {
+- const id = match.params.id;
++ const MyComponent: React.FC = () => {
++ const { id } = useParams<{ id: string }>();
+```
+
+**RouteComponentProps Removed**
+
+The `RouteComponentProps` type and its `history`, `location`, and `match` props are no longer available in React Router v6. Use the equivalent hooks instead:
+
+- `history` -> `useNavigate` (see below) or `useIonRouter`
+- `match.params` -> `useParams` (covered above)
+- `location` -> `useLocation`
+
+```diff
+- import { RouteComponentProps } from 'react-router-dom';
++ import { useNavigate, useLocation } from 'react-router-dom';
++ import { useIonRouter } from '@ionic/react';
+
+- const MyComponent: React.FC = ({ history, location }) => {
+- history.push('/path');
+- history.replace('/path');
+- history.goBack();
+- console.log(location.pathname);
++ const MyComponent: React.FC = () => {
++ const navigate = useNavigate();
++ const router = useIonRouter();
++ const location = useLocation();
++ // In an event handler or useEffect:
++ navigate('/path');
++ navigate('/path', { replace: true });
++ router.goBack();
++ console.log(location.pathname);
+```
+
+**Exact Prop Removed**
+
+The `exact` prop is no longer needed. React Router v6 routes match exactly by default. To match sub-paths, use a `/*` suffix on the path:
+
+```diff
+-
++
+```
+
+**Render Prop Removed**
+
+The `render` prop has been replaced with the `element` prop:
+
+```diff
+- } />
++ } />
+```
+
+**Programmatic Navigation**
+
+The `useHistory` hook has been replaced with `useNavigate`:
+
+```diff
+- import { useHistory } from 'react-router-dom';
++ import { useNavigate } from 'react-router-dom';
++ import { useIonRouter } from '@ionic/react';
+
+- const history = useHistory();
++ const navigate = useNavigate();
++ const router = useIonRouter();
+
+- history.push('/path');
++ navigate('/path');
+
+- history.replace('/path');
++ navigate('/path', { replace: true });
+
+- history.goBack();
++ router.goBack();
+```
+
+**Custom History Prop Removed**
+
+The `history` prop has been removed from `IonReactRouter`, `IonReactHashRouter`, and `IonReactMemoryRouter`. React Router v6's `BrowserRouter`, `HashRouter`, and `MemoryRouter` no longer accept custom `history` objects.
+
+```diff
+- import { createBrowserHistory } from 'history';
+- const history = createBrowserHistory();
+-
++
+```
+
+For `IonReactMemoryRouter` (commonly used in tests), use `initialEntries` instead:
+
+```diff
+- import { createMemoryHistory } from 'history';
+- const history = createMemoryHistory({ initialEntries: ['/start'] });
+-
++
+```
+
+**IonRedirect Removed**
+
+The `IonRedirect` component has been removed. Use React Router's `` component instead:
+
+```diff
+- import { IonRedirect } from '@ionic/react';
+-
++ import { Navigate } from 'react-router-dom';
++ } />
+```
+
+**Path Regex Constraints Removed**
+
+React Router v6 no longer supports regex constraints in path parameters (e.g., `/:tab(sessions)`). Use literal paths instead:
+
+```diff
+-
+-
++ } />
++ } />
+```
+
+**IonRoute API Changes**
+
+The `IonRoute` component follows the same API changes as React Router's ``. The `render` prop has been replaced with `element`, and the `exact` prop has been removed:
+
+```diff
+- } />
++ } />
+```
+
+For more information on migrating from React Router v5 to v6, refer to the [React Router v6 Upgrade Guide](https://reactrouter.com/6.28.0/upgrading/v5).
+
+
Vue
+
+The `@ionic/vue-router` package now requires Vue Router v5. Vue Router v4 is no longer supported. Vue Router v5 also raises its peer requirement on Vue itself, so the minimum supported Vue version moves to `3.5.0`.
+
+**Minimum Version Requirements**
+| Package | Supported Version |
+| ---------- | ----------------- |
+| vue-router | 5.0.0+ |
+| vue | 3.5.0+ |
+
+**Migration**
+
+Vue Router 5 is a transition release that ships no runtime breaking changes for Vue Router 4 consumers, so no application code changes are required for routes, navigation guards, or the `IonRouterOutlet`. Bump the dep ranges in your app's `package.json`:
+
+```diff
+ "dependencies": {
+- "vue": "^3.4.0",
+- "vue-router": "^4.0.0"
++ "vue": "^3.5.0",
++ "vue-router": "^5.0.0"
+ }
+```
+
+**Deprecation Warning for `next()` in Navigation Guards**
+
+Vue Router 5 prints a deprecation warning when `next()` is called inside `beforeRouteLeave`, `beforeRouteEnter`, `beforeRouteUpdate`, or `router.beforeEach`. The callback form still works, but Vue Router 6 will remove it. Migrate to the return-value pattern:
+
+```diff
+ // Composition API
+ onBeforeRouteLeave((to, from) => {
+- if (!confirm('Leave?')) return next(false);
+- next();
++ if (!confirm('Leave?')) return false;
++ return true;
+ });
+```
+
+```diff
+ // Options API
+ beforeRouteLeave(to, from, next) {
+- if (!confirm('Leave?')) return next(false);
+- next();
++ beforeRouteLeave(to, from) {
++ if (!confirm('Leave?')) return false;
++ return true;
+ }
+```
+
+For more information on Vue Router 5, refer to the [Vue Router v4-to-v5 migration guide](https://router.vuejs.org/guide/migration/v4-to-v5.html).
diff --git a/BREAKING_ARCHIVE/v4.md b/BREAKING_ARCHIVE/v4.md
new file mode 100644
index 00000000000..7a791bc0960
--- /dev/null
+++ b/BREAKING_ARCHIVE/v4.md
@@ -0,0 +1,1962 @@
+
+# Breaking Changes
+
+## Migrating
+
+### Migration Guide
+
+If you aren't sure where to start in upgrading to v4, we recommend reading through our [migration guide](https://ionicframework.com/docs/building/migration) first.
+
+### Migration Linter
+
+Looking for a tool that automatically warns (and sometimes fixes) the breaking changes listed? Check out our [migration linter](https://github.com/ionic-team/v4-migration-tslint)!
+
+
+## Components
+
+A list of the breaking changes introduced to each component in Ionic Angular v4.
+
+- [Action Sheet](#action-sheet)
+- [Alert](#alert)
+- [Back Button](#back-button)
+- [Button](#button)
+- [Colors](#colors)
+- [Component Imports](#component-imports)
+- [Content](#content)
+- [Datetime](#datetime)
+- [Dynamic Mode](#dynamic-mode)
+- [FAB](#fab)
+- [Fixed Content](#fixed-content)
+- [Grid](#grid)
+- [Icon](#icon)
+- [Infinite Scroll](#infinite-scroll)
+- [Item](#item)
+- [Item Divider](#item-divider)
+- [Item Options](#item-options)
+- [Item Sliding](#item-sliding)
+- [Label](#label)
+- [List Header](#list-header)
+- [Loading](#loading)
+- [Menu](#menu)
+- [Menu Toggle](#menu-toggle)
+- [Modal](#modal)
+- [Nav](#nav)
+- [Navbar](#navbar)
+- [Option](#option)
+- [Overlays](#overlays)
+- [Popover](#popover)
+- [Radio](#radio)
+- [Range](#range)
+- [Refresher](#refresher)
+- [Scroll](#scroll)
+- [Segment Button](#segment-button)
+- [Select](#select)
+- [Show When / Hide When](#show-when--hide-when)
+- [Spinner](#spinner)
+- [Tabs](#tabs)
+- [Text / Typography](#text--typography)
+- [Theming](#theming)
+- [Toast](#toast)
+- [Toolbar](#toolbar)
+
+
+## Action Sheet
+
+The `title`, `subTitle` and `enableBackdropDismiss` properties have been renamed to `header`, `subHeader` and `backdropDismiss` respectively.
+
+**Old Usage Example:**
+
+```js
+const actionSheet = await actionSheetCtrl.create({
+ title: 'This is the title',
+ subTitle: 'this is the sub title',
+ enableBackdropDismiss: false
+});
+await actionSheet.present();
+```
+
+**New Usage Example:**
+
+```js
+const actionSheet = await actionSheetCtrl.create({
+ header: 'This is the title',
+ subHeader: 'this is the sub title',
+ backdropDismiss: false
+});
+await actionSheet.present();
+```
+
+
+## Alert
+
+The `title`, `subTitle` and `enableBackdropDismiss` properties have been renamed to `header`, `subHeader` and `backdropDismiss` respectively.
+
+**Old Usage Example:**
+
+```js
+const alert = await alertCtrl.create({
+ title: 'This is the title',
+ subTitle: 'this is the sub title',
+ enableBackdropDismiss: false
+});
+await alert.present();
+```
+
+**New Usage Example:**
+
+```js
+const alert = await alertCtrl.create({
+ header: 'This is the title',
+ subHeader: 'this is the sub title',
+ backdropDismiss: false
+});
+await alert.present();
+```
+
+
+## Back Button
+
+The back button is no longer added by default to a navigation bar. It should be explicitly written in a toolbar:
+
+**Old Usage Example:**
+
+```html
+
+ Back Button Example
+
+```
+
+**New Usage Example:**
+
+```html
+
+
+
+
+ Back Button Example
+
+```
+
+See the [back button documentation](https://github.com/ionic-team/ionic/blob/main/core/src/components/back-button) for more usage examples.
+
+## Button
+
+### Markup Changed
+
+Button should now be written as an `` element. Ionic will determine when to render an anchor tag based on the presence of an `href` attribute.
+
+**Old Usage Example:**
+
+```html
+
+
+
+ Default Anchor
+
+```
+
+**New Usage Example:**
+
+```html
+
+ Default Button
+
+
+
+ Default Anchor
+
+```
+
+### Attributes Renamed
+
+Previously to style icons inside of a button the following attributes were used: `icon-left`, `icon-right`, (and with the added support of RTL) `icon-start`, `icon-end`.
+
+These have been renamed to the following, and moved from the button element to the icon itself:
+
+| Old Property | New Property | Property Behavior |
+|---------------------------|----------------|-----------------------------------------------------------------------|
+| `icon-left`, `icon-start` | `slot="start"` | Positions to the left of the button in LTR, and to the right in RTL. |
+| `icon-right`, `icon-end` | `slot="end"` | Positions to the right of the button in LTR, and to the left in RTL. |
+
+In addition, several sets of mutually exclusive boolean attributes have been combined into a single string attribute.
+
+The `small` and `large` attributes are now combined under the `size` attribute. The `clear`, `outline`, and `solid` attributes have been combined under `fill`. The `full` and `block` attributes have been combined under `expand`. And, lastly, the `round` attribute is now used under `shape`.
+
+| Old Property | New Property | Property Behavior |
+| --------------------------- | ------------ | --------------------------- |
+| `small`, `large` | `size` | Sets the button size. |
+| `clear`, `outline`, `solid` | `fill` | Sets the button fill style. |
+| `full`, `block` Β Β Β Β Β Β | `expand` | Sets the button width. |
+| `round` Β Β Β Β Β Β | `shape` | Sets the button shape. |
+
+
+**Old Usage Example:**
+
+```html
+
+
+ Icon Left
+
+
+
+
+ Icon Left on LTR, Right on RTL
+
+
+
+ Icon Right
+
+
+
+
+ Icon Right on LTR, Left on RTL
+
+
+
+
+ Large Button
+
+
+
+ Outline Button
+
+
+
+ Full-width Button
+
+
+
+ Round Button
+
+```
+
+**New Usage Example:**
+
+```html
+
+
+ Icon Left on LTR, Right on RTL
+
+
+
+ Icon Right on LTR, Left on RTL
+
+
+
+
+ Large Button
+
+
+
+ Outline Button
+
+
+
+ Full-width Button
+
+
+
+ Round Button
+
+```
+
+
+## Colors
+
+The default Ionic theme colors have changed. Previously we had:
+
+```
+primary: #327eff
+secondary: #32db64
+danger: #f53d3d
+light: #f4f4f4
+dark: #222
+```
+
+Some of their values have changed, and we now include more colors by default:
+
+```
+primary: #3880ff
+secondary: #0cd1e8
+tertiary: #7044ff
+success: #10dc60
+warning: #ffce00
+danger: #f04141
+light: #f4f5f8
+medium: #989aa2
+dark: #222428
+```
+
+The `secondary` color saw the largest change. If you were previously using our `secondary` color we recommend switching to `success` instead.
+
+
+## Component Imports
+
+For consistency with other frameworks and the rest of APIs and tooling, the exported
+Ionic components are now prefixed with `Ion`:
+
+```diff
+- import { Input, List, Slides } from 'ionic-angular';
++ import { IonInput, IonList, IonSlides } from '@ionic/angular';
+```
+
+
+## Content
+
+Content is now a drop-in replacement for `ion-scroll`. This makes `ion-content` much more flexible. It can be used anywhere, even nested.
+
+### Method Removed
+
+The `resize` method has been removed from Content. In Ionic 4, the `ion-content` is based on a flex layout. This means the content size will automatically adjust without requiring a call to `resize()`.
+
+
+### Attributes Renamed
+
+
+| Old Property | New Property | Property Behavior |
+|--------------|-----------------------|-------------------------------------------------------------------------|
+| no-bounce | forceOverflow="false" | If true and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. |
+
+
+## Datetime
+
+The Datetime classes and interfaces have changed capitalization from `DateTime` to `Datetime`. This is more consistent with other components and their tags.
+
+**Old Usage Example:**
+
+```javascript
+import { DateTime } from 'ionic-angular';
+```
+
+**New Usage Example:**
+
+```javascript
+import { Datetime } from '@ionic/angular';
+```
+
+
+## Dynamic Mode
+
+Components are no longer able to have their mode changed dynamically. You can change the mode before the first render, but after that it will not style properly because only the initial mode's styles are included.
+
+
+## Events
+
+Events now emit as a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) interface that extends the [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface. This interface includes a `detail` property that holds any data passed when the event is triggered.
+
+This allows you to still get the details of the event. For example, to get the target where the event was dispatched, such as a button that was clicked, you can read in the value of `event.target`.
+
+**Old Usage Example:**
+
+```html
+
+```
+
+```typescript
+onSelectChange(event) {
+ const value = event.value;
+ console.log('Select value is', value);
+}
+```
+
+**New Usage Example:**
+
+```typescript
+onSelectChange(event: CustomEvent) {
+ const value = event.detail.value;
+ console.log('Select value is', value);
+}
+```
+
+
+## FAB
+
+### Markup Changed
+
+Buttons inside of an `` container should now be written as an `` element. Ionic will determine when to render an anchor tag based on the presence of an `href` attribute.
+
+**Old Usage Example:**
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+**New Usage Example:**
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Attributes Renamed
+
+The mutually exclusive boolean attributes to position the fab have been combined into two single string attributes.
+
+The attributes to align the fab horizontally are now combined under the `horizontal` attribute and the attributes to align the fab vertically are now combined under the `vertical` attribute:
+
+| Old Property | New Property | Property Behavior |
+|--------------|----------------------|-------------------------------------------------------------------------|
+| left | Removed, see `start` | |
+| right | Removed, see `end` | |
+| center | `horizontal="center"`| Positions to the center of the viewport. |
+| start | `horizontal="start"` | Positions to the left of the viewport in LTR, and to the right in RTL. |
+| end | `horizontal="end"` | Positions to the right of the viewport in LTR, and to the left in RTL. |
+| top | `vertical="top"` | Positions at the top of the viewport. |
+| bottom | `vertical="bottom"` | Positions at the bottom of the viewport. |
+| middle | `vertical="center"` | Positions at the center of the viewport. |
+
+_Note that `middle` has been changed to `center` for the vertical positioning._
+
+**Old Usage Example:**
+
+```html
+
+
+
+
+
+
+
+```
+
+**New Usage Example:**
+
+```html
+
+
+
+
+
+
+
+```
+
+## Fixed Content
+
+The `` container was previously placed inside of the fixed content by default. Now, any fixed content should use the `fixed` slot.
+
+**Old Usage Example:**
+
+```html
+
+
+
+
+ Scrollable Content
+
+```
+
+**New Usage Example:**
+
+```html
+
+
+
+
+ Scrollable Content
+
+```
+
+## Grid
+
+### Markup Changed
+
+The Grid has been refactored in order to support CSS variables and a dynamic number of columns. The following column attributes have been changed.
+
+_In the following examples, `{breakpoint}` refers to the optional screen breakpoint (xs, sm, md, lg, xl) and `{value}` refers to the number of columns (`auto` or a number between `1` and `12`)._
+
+- `col-{breakpoint}-{value}` attributes have been renamed to `size-{breakpoint}=β{value}β`
+- `offset-{breakpoint}-{value}` attributes have been renamed to `offset-{breakpoint}=β{value}β`
+- `push-{breakpoint}-{value}` attributes have been renamed to `push-{breakpoint}=β{value}β`
+- `pull-{breakpoint}-{value}` attributes have been renamed to `pull-{breakpoint}=β{value}β`
+
+Customizing the padding and width of a grid should now be done with CSS variables. For more information, see [Grid Layout](https://github.com/ionic-team/ionic-docs/blob/main/src/pages/layout/grid.md).
+
+## Icon
+
+### Fonts Removed
+
+Icons have been refactored to use SVGs instead of fonts. Ionic will only fetch the SVG for the icon when it is needed, instead of having a large font file that is always loaded in.
+
+If any `CSS` is being overridden for an icon it will need to change to override the SVG itself. Below is a usage example of the differences in changing the icon color.
+
+**Old Usage Example:**
+
+```css
+.icon {
+ color: #000;
+}
+```
+
+**New Usage Example:**
+
+```css
+ion-icon {
+ fill: #000;
+}
+```
+
+_Note: we are no longer adding the `icon` class to an `ion-icon`, so the element should be targeted instead._
+
+
+### Property Removed
+
+The `isActive` property has been removed. It only worked for `ios` icons previously. If you would like to switch between an outline and solid icon you should set it in the `name`, or `ios`/`md` attribute and then change it when needed.
+
+## Infinite Scroll
+
+### Method Removed
+
+The `enable()` method has been removed in favor of using the `disabled` property on the `ion-infinite-scroll` element.
+
+**Old Usage Example:**
+
+```html
+
+
+
+```
+
+```javascript
+doInfinite(infiniteScroll) {
+ console.log('Begin async operation');
+
+ setTimeout(() => {
+ console.log('Async operation has ended');
+ infiniteScroll.complete();
+
+ // To disable the infinite scroll
+ infiniteScroll.enable(false);
+ }, 500);
+}
+```
+
+**New Usage Example:**
+
+```html
+
+
+
+```
+
+```javascript
+doInfinite(event) {
+ console.log('Begin async operation');
+
+ setTimeout(() => {
+ console.log('Async operation has ended');
+ event.target.complete();
+
+ // To disable the infinite scroll
+ event.target.disabled = true;
+ }, 500);
+}
+```
+
+
+## Item
+
+### Markup Changed
+
+Item should now be written as an `` element. Ionic will determine when to render an anchor tag based on the presence of an `href` attribute, and a button tag based on the presence of an `onclick` or `button` attribute. Otherwise, it will render a div.
+
+**Old Usage Example:**
+
+```html
+
+ Default Item
+
+
+
+
+
+ Anchor Item
+
+```
+
+**New Usage Example:**
+
+```html
+
+
+ Default Item
+
+
+
+
+
+ Button Item
+
+
+
+
+
+ Anchor Item
+
+
+```
+
+### Label Required
+
+Previously an `ion-label` would automatically get added to an `ion-item` if one wasn't provided. Now an `ion-label` should always be added in the item component.
+
+```html
+
+
+ Item Label
+
+
+```
+
+### Attributes Renamed
+
+Previously to position elements inside of an `ion-item` the following attributes were used: `item-left`, `item-right`, (and with the added support of RTL) `item-start`, `item-end`.
+
+These have been renamed to the following:
+
+| Old Property | New Property | Property Behavior |
+|---------------------------|----------------|---------------------------------------------------------------------|
+| `item-left`, `item-start` | `slot="start"` | Positions to the left of the item in LTR, and to the right in RTL. |
+| `item-right`, `item-end` | `slot="end"` | Positions to the right of the item in LTR, and to the left in RTL. |
+
+
+**Old Usage Example:**
+
+```html
+
+