* refactor(room-list): migrate SpaceStore off the legacy room list store SpaceStore.setActiveRoomInSpace iterated the legacy RoomListStore's `orderedLists` in `TAG_ORDER`; switch it to the space-aware RoomListStoreV3.getSortedRoomsInActiveSpace() accessor. This drops the last non-UI dependency on the legacy store and on `TAG_ORDER` (exported from LegacyRoomList, deleted next). * feat(room-list)!: remove the legacy room list UI Delete the old sublist-based room list and its components now that the new RoomListPanel is the default. Removed: LegacyRoomList, LegacyRoomListHeader, RoomSublist, ExtraTile, RoomTile (+ Subtitle/ CallSummary), RoomBreadcrumbs and RoomSearch, plus their styles and tests. LeftPanel collapses to the RoomListPanel-only path. The shared `contextMenuBelow` helper is relocated into RoomResultContextMenus (its only remaining consumer). * feat(room-list)!: remove the legacy RoomListStore The legacy sublist-based room list UI is gone, so the old `stores/room-list` store (Algorithm, sorters, filters, layout store, space watcher) has no remaining consumers. Delete the directory and its tests. MatrixChat.forgetRoom no longer calls the legacy `manualRoomUpdate`; the new room list store removes the room on the `AfterForgetRoom` dispatch that still fires. Drop the `mxRoomListStore`/`mxRoomListLayoutStore` globals and the now-dead test imports. * feat(room-list)!: remove the feature_new_room_list labs flag The new room list is now the only room list, so remove the feature_new_room_list labs flag and make its enabled behaviour unconditional everywhere it was gated: - LoggedInView: always use the resizable layout and NEW_ROOM_LIST_MIN_WIDTH; drop the collapsible/minimized legacy path. - SpaceStore: People and Favourites are dropped from metaSpaceOrder (per the long-standing TODO on the removed accessor). - MessagePreviewStore: stop appending thread replies to previews. - Settings, SidebarUserSettingsTab, PreferencesUserSettingsTab, QuickSettingsButton, SpacePanel, LandmarkNavigation: drop the flag reads and legacy branches. Update the tests that toggled the flag; the People/Favourites meta space tests covered behaviour that the flag (default on) already disabled. * feat(room-list)!: remove the dead legacy left-panel resizer LoggedInView still built the old `Resizer`/`CollapseDistributor` over an `lp-resizer` ResizeHandle and persisted `mx_lhs_size`. That handle is no longer rendered (the resizable layout is now driven by LeftResizablePanelView + ResizerViewModel, which persists its own state via RoomList.panelSize/RoomList.isPanelCollapsed), so the old resizer was inert dead code left over from the legacy room list. Remove createResizer/loadResizer/loadResizerPreferences, the _resizeContainer/resizeHandler refs, the ResizeHandle render, the mx_lhs_size handling and NEW_ROOM_LIST_MIN_WIDTH, plus the unit tests that exercised the mocked resizer. * feat(room-list)!: update i18n files * refactor(room-list): remove the now-unused collapseLhs state `collapseLhs` is write-only since the left panel no longer collapses: it was last read by LoggedInView's `shouldUseMinimizedUI`, removed with the feature_new_room_list flag. Drop it from MatrixChat's IState (and its assignments), collapsing the hide/show_left_panel handlers to just the `notifyLeftHandleResized()` call they still need, and from LoggedInView's IProps and the test props. * fix(room-list): instantiate message previewers lazily Removing the unused SettingsStore import from MessagePreviewStore (when the feature_new_room_list flag was dropped) changed module load order and exposed a latent circular dependency: ReactionEventPreview imports MessagePreviewStore, which eagerly did `new ReactionEventPreview()` at module-eval — so importing ReactionEventPreview first (as its unit test does) hit "ReactionEventPreview is not a constructor". Construct the previewers lazily on first use (cached) instead of at module load, so nothing dereferences a mid-evaluation module. Fixes ReactionEventPreview-test. * test(room-list): remove `feature_new_room_list` labs flag in e2e tests * chore: remove remaining `newRoomList` flag * chore: cleanup theme files * fix: restore the re-resizable TouchEvent polyfill * chore: remove usage of breadcrumbs settings in BreadcrumbStore * Revert "fix(room-list): instantiate message previewers lazily" This reverts commit 4e6eedfff0449c68a96c0470a4eb425b5aec5512. * chore: remove unused function in BreadCrumbStore * test: remove unused fuction of BreadcrumStore in tests * test: add tests for RoomResultContextMenu
Element Desktop
Element Desktop is a Matrix client for desktop platforms with Element Web at its core.
First Steps
Before you do anything else, fetch the dependencies:
pnpm install
Fetching Element
Since this package is just the Electron wrapper for Element Web, it doesn't contain any of the Element Web code, so the first step is to get a working copy of Element Web. There are a few ways of doing this:
# Fetch the prebuilt release Element package from the element-web GitHub releases page. The version
# fetched will be the same as the local element-desktop package.
# We're explicitly asking for no config, so the packaged Element will have no config.json.
pnpm run fetch --noverify --cfgdir ""
...or if you'd like to use GPG to verify the downloaded package:
# Fetch the Element public key from the element.io web server over a secure connection and import
# it into your local GPG keychain (you'll need GPG installed). You only need to to do this
# once.
pnpm run fetch --importkey
# Fetch the package and verify the signature
pnpm run fetch --cfgdir ""
...or either of the above, but fetching a specific version of Element:
# Fetch the prebuilt release Element package from the element-web GitHub releases page. The version
# fetched will be the same as the local element-desktop package.
pnpm run fetch --noverify --cfgdir "" v1.5.6
If you only want to run the app locally and don't need to build packages, you can
provide the webapp directory directly:
ln -s ../web/webapp ./
[TODO: add support for fetching develop builds, arbitrary URLs and arbitrary paths]
Building
Native Build
TODO: List native pre-requisites
Optionally, build the native modules, which include support for searching in encrypted rooms and secure storage. Skipping this step is fine, you just won't have those features.
Then, run
pnpm run build
This will do a couple of things:
- Run the
setversionscript to set the local package version to match whatever version of Element you installed above. - Run electron-builder to build a package. The package built will match the operating system you're running the build process on.
Docker
Alternatively, you can also build using docker, which will always produce the linux package:
# Run this once to make the docker image
pnpm run docker:setup
pnpm run docker:install
# if you want to build the native modules (this will take a while)
pnpm run docker:build:native
pnpm run docker:build
After running, the packages should be in dist/.
Starting
If you'd just like to run the electron app locally for development:
pnpm start
Config
If you'd like the packaged Element to have a configuration file, you can create a
config directory and place config.json in there, then specify this directory
with the --cfgdir option to pnpm run fetch, eg:
mkdir myconfig
cp /path/to/my/config.json myconfig/
pnpm run fetch --cfgdir myconfig
The config dir for the official Element app is in element.io. If you use this,
your app will auto-update itself using builds from element.io.
Profiles
To run multiple instances of the desktop app for different accounts, you can
launch the executable with the --profile argument followed by a unique
identifier, e.g element-desktop --profile Work for it to run a separate profile and
not interfere with the default one.
Alternatively, a custom location for the profile data can be specified using the
--profile-dir flag followed by the desired path.
User-specified config.json
%APPDATA%\$NAME\config.jsonon Windows$XDG_CONFIG_HOME/$NAME/config.jsonor~/.config/$NAME/config.jsonon Linux~/Library/Application Support/$NAME/config.jsonon macOS
In the paths above, $NAME is typically Element, unless you use --profile $PROFILE in which case it becomes Element-$PROFILE, or it is using one of
the above created by a pre-1.7 install, in which case it will be Riot or
Riot-$PROFILE.
You may also specify a different path entirely for the config.json file by
providing the --config $YOUR_CONFIG_JSON_FILE to the process, or via the
ELEMENT_DESKTOP_CONFIG_JSON environment variable.
Translations
To add a new translation, head to the translating doc.
For a developer guide, see the translating dev doc.
Report bugs & give feedback
If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.
To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it. Please note that this issue tracker is associated with the element-web repo, but is also applied to the code in this repo as well.
Copyright & License
Copyright (c) 2016-2017 OpenMarket Ltd
Copyright (c) 2017 Vector Creations Ltd
Copyright (c) 2017-2025 New Vector Ltd
This software is multi licensed by New Vector Ltd (Element). It can be used either:
(1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR
(2) for free under the terms of the GNU General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR
(3) under the terms of a paid-for Element Commercial License agreement between you and Element (the terms of which may vary depending on what you and Element have agreed to). Unless required by applicable law or agreed to in writing, software distributed under the Licenses is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licenses for the specific language governing permissions and limitations under the Licenses.