Merge branch 'master' into feature/webkitgtk6

This commit is contained in:
Lea Anthony 2026-01-04 11:46:22 +11:00 committed by GitHub
commit e4c15da18a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 523 additions and 169 deletions

View file

@ -123,7 +123,15 @@ func (b *Bindings) GenerateModels() ([]byte, error) {
// if we have enums for this package, add them as well
var enums, enumsExist = b.enumsToGenerateTS[packageName]
if enumsExist {
for enumName, enum := range enums {
// Sort the enum names first to make the output deterministic
sortedEnumNames := make([]string, 0, len(enums))
for enumName := range enums {
sortedEnumNames = append(sortedEnumNames, enumName)
}
sort.Strings(sortedEnumNames)
for _, enumName := range sortedEnumNames {
enum := enums[enumName]
fqemumname := packageName + "." + enumName
if seen.Contains(fqemumname) {
continue
@ -172,7 +180,7 @@ func (b *Bindings) GenerateModels() ([]byte, error) {
}
// Sort the package names first to make the output deterministic
sortedPackageNames := make([]string, 0)
sortedPackageNames := make([]string, 0, len(models))
for packageName := range models {
sortedPackageNames = append(sortedPackageNames, packageName)
}

View file

@ -0,0 +1,271 @@
package binding_test
// Test for PR #4664: Fix generated enums ordering
// This test ensures that enum output is deterministic regardless of map iteration order
// ZFirstEnum - named with Z prefix to test alphabetical sorting
type ZFirstEnum int
const (
ZFirstEnumValue1 ZFirstEnum = iota
ZFirstEnumValue2
)
var AllZFirstEnumValues = []struct {
Value ZFirstEnum
TSName string
}{
{ZFirstEnumValue1, "ZValue1"},
{ZFirstEnumValue2, "ZValue2"},
}
// ASecondEnum - named with A prefix to test alphabetical sorting
type ASecondEnum int
const (
ASecondEnumValue1 ASecondEnum = iota
ASecondEnumValue2
)
var AllASecondEnumValues = []struct {
Value ASecondEnum
TSName string
}{
{ASecondEnumValue1, "AValue1"},
{ASecondEnumValue2, "AValue2"},
}
// MMiddleEnum - named with M prefix to test alphabetical sorting
type MMiddleEnum int
const (
MMiddleEnumValue1 MMiddleEnum = iota
MMiddleEnumValue2
)
var AllMMiddleEnumValues = []struct {
Value MMiddleEnum
TSName string
}{
{MMiddleEnumValue1, "MValue1"},
{MMiddleEnumValue2, "MValue2"},
}
type EntityWithMultipleEnums struct {
Name string `json:"name"`
EnumZ ZFirstEnum `json:"enumZ"`
EnumA ASecondEnum `json:"enumA"`
EnumM MMiddleEnum `json:"enumM"`
}
func (e EntityWithMultipleEnums) Get() EntityWithMultipleEnums {
return e
}
// EnumOrderingTest tests that multiple enums in the same package are output
// in alphabetical order by enum name. Before PR #4664, the order was
// non-deterministic due to Go map iteration order.
var EnumOrderingTest = BindingTest{
name: "EnumOrderingTest",
structs: []interface{}{
&EntityWithMultipleEnums{},
},
enums: []interface{}{
// Intentionally add enums in non-alphabetical order
AllZFirstEnumValues,
AllASecondEnumValues,
AllMMiddleEnumValues,
},
exemptions: nil,
shouldError: false,
TsGenerationOptionsTest: TsGenerationOptionsTest{
TsPrefix: "",
TsSuffix: "",
},
// Expected output should have enums in alphabetical order: ASecondEnum, MMiddleEnum, ZFirstEnum
want: `export namespace binding_test {
export enum ASecondEnum {
AValue1 = 0,
AValue2 = 1,
}
export enum MMiddleEnum {
MValue1 = 0,
MValue2 = 1,
}
export enum ZFirstEnum {
ZValue1 = 0,
ZValue2 = 1,
}
export class EntityWithMultipleEnums {
name: string;
enumZ: ZFirstEnum;
enumA: ASecondEnum;
enumM: MMiddleEnum;
static createFrom(source: any = {}) {
return new EntityWithMultipleEnums(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.enumZ = source["enumZ"];
this.enumA = source["enumA"];
this.enumM = source["enumM"];
}
}
}
`,
}
// EnumElementOrderingEnum tests sorting of enum elements by TSName
type EnumElementOrderingEnum string
const (
EnumElementZ EnumElementOrderingEnum = "z_value"
EnumElementA EnumElementOrderingEnum = "a_value"
EnumElementM EnumElementOrderingEnum = "m_value"
)
// AllEnumElementOrderingValues intentionally lists values out of alphabetical order
// to test that AddEnum sorts them
var AllEnumElementOrderingValues = []struct {
Value EnumElementOrderingEnum
TSName string
}{
{EnumElementZ, "Zebra"},
{EnumElementA, "Apple"},
{EnumElementM, "Mango"},
}
type EntityWithUnorderedEnumElements struct {
Name string `json:"name"`
Enum EnumElementOrderingEnum `json:"enum"`
}
func (e EntityWithUnorderedEnumElements) Get() EntityWithUnorderedEnumElements {
return e
}
// EnumElementOrderingTest tests that enum elements are sorted alphabetically
// by their TSName within an enum. Before PR #4664, elements appeared in the
// order they were added, which could be arbitrary.
var EnumElementOrderingTest = BindingTest{
name: "EnumElementOrderingTest",
structs: []interface{}{
&EntityWithUnorderedEnumElements{},
},
enums: []interface{}{
AllEnumElementOrderingValues,
},
exemptions: nil,
shouldError: false,
TsGenerationOptionsTest: TsGenerationOptionsTest{
TsPrefix: "",
TsSuffix: "",
},
// Expected output should have enum elements sorted: Apple, Mango, Zebra
want: `export namespace binding_test {
export enum EnumElementOrderingEnum {
Apple = "a_value",
Mango = "m_value",
Zebra = "z_value",
}
export class EntityWithUnorderedEnumElements {
name: string;
enum: EnumElementOrderingEnum;
static createFrom(source: any = {}) {
return new EntityWithUnorderedEnumElements(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.enum = source["enum"];
}
}
}
`,
}
// TSNameEnumElementOrdering tests sorting with TSName() method enum
type TSNameEnumElementOrdering string
const (
TSNameEnumZ TSNameEnumElementOrdering = "z_value"
TSNameEnumA TSNameEnumElementOrdering = "a_value"
TSNameEnumM TSNameEnumElementOrdering = "m_value"
)
func (v TSNameEnumElementOrdering) TSName() string {
switch v {
case TSNameEnumZ:
return "Zebra"
case TSNameEnumA:
return "Apple"
case TSNameEnumM:
return "Mango"
default:
return "Unknown"
}
}
// AllTSNameEnumValues intentionally out of order
var AllTSNameEnumValues = []TSNameEnumElementOrdering{TSNameEnumZ, TSNameEnumA, TSNameEnumM}
type EntityWithTSNameEnumOrdering struct {
Name string `json:"name"`
Enum TSNameEnumElementOrdering `json:"enum"`
}
func (e EntityWithTSNameEnumOrdering) Get() EntityWithTSNameEnumOrdering {
return e
}
// TSNameEnumElementOrderingTest tests that enums using TSName() method
// also have their elements sorted alphabetically by the TSName.
var TSNameEnumElementOrderingTest = BindingTest{
name: "TSNameEnumElementOrderingTest",
structs: []interface{}{
&EntityWithTSNameEnumOrdering{},
},
enums: []interface{}{
AllTSNameEnumValues,
},
exemptions: nil,
shouldError: false,
TsGenerationOptionsTest: TsGenerationOptionsTest{
TsPrefix: "",
TsSuffix: "",
},
// Expected output should have enum elements sorted: Apple, Mango, Zebra
want: `export namespace binding_test {
export enum TSNameEnumElementOrdering {
Apple = "a_value",
Mango = "m_value",
Zebra = "z_value",
}
export class EntityWithTSNameEnumOrdering {
name: string;
enum: TSNameEnumElementOrdering;
static createFrom(source: any = {}) {
return new EntityWithTSNameEnumOrdering(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.enum = source["enum"];
}
}
}
`,
}

View file

@ -55,6 +55,10 @@ func TestBindings_GenerateModels(t *testing.T) {
Generics2Test,
IgnoredTest,
DeepElementsTest,
// PR #4664: Enum ordering tests
EnumOrderingTest,
EnumElementOrderingTest,
TSNameEnumElementOrderingTest,
}
testLogger := &logger.Logger{}

View file

@ -9,6 +9,7 @@
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#import "CustomProtocol.h"
#import "message.h"
@implementation AppDelegate
@ -19,6 +20,17 @@
return YES;
}
- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<NSUserActivityRestoring>> * _Nullable))restorationHandler {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
NSURL *url = userActivity.webpageURL;
if (url) {
HandleOpenURL((char*)[[url absoluteString] UTF8String]);
return YES;
}
}
return NO;
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return NO;
}

View file

@ -3,7 +3,7 @@
#import <Cocoa/Cocoa.h>
extern void HandleCustomProtocol(char*);
extern void HandleOpenURL(char*);
@interface CustomProtocolSchemeHandler : NSObject
+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent;

View file

@ -6,7 +6,7 @@
NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
HandleCustomProtocol((char*)[[[event paramDescriptorForKeyword:keyDirectObject] stringValue] UTF8String]);
HandleOpenURL((char*)[[[event paramDescriptorForKeyword:keyDirectObject] stringValue] UTF8String]);
}
@end

View file

@ -518,8 +518,8 @@ func HandleOpenFile(filePath *C.char) {
openFilepathBuffer <- goFilepath
}
//export HandleCustomProtocol
func HandleCustomProtocol(url *C.char) {
//export HandleOpenURL
func HandleOpenURL(url *C.char) {
goUrl := C.GoString(url)
openUrlBuffer <- goUrl
}

View file

@ -2,6 +2,7 @@ package typescriptify
import (
"bufio"
"cmp"
"fmt"
"io"
"log"
@ -9,6 +10,7 @@ import (
"path"
"reflect"
"regexp"
"slices"
"strings"
"time"
@ -372,6 +374,9 @@ func (t *TypeScriptify) AddEnum(values interface{}) *TypeScriptify {
elements = append(elements, el)
}
slices.SortFunc(elements, func(a, b enumElement) int {
return cmp.Compare(a.name, b.name)
})
ty := reflect.TypeOf(elements[0].value)
t.enums[ty] = elements
t.enumTypes = append(t.enumTypes, EnumType{Type: ty})
@ -516,9 +521,6 @@ func (t TypeScriptify) ConvertToFile(fileName string, packageName string) error
if _, err := f.WriteString(converted); err != nil {
return err
}
if err != nil {
return err
}
return nil
}

View file

@ -10,6 +10,10 @@ The pattern used by the default templates are that `main.go` is used for configu
The `app.go` file will define a struct that has 2 methods which act as hooks into the main application:
```go title="app.go"
import (
"context"
)
type App struct {
ctx context.Context
}
@ -28,7 +32,7 @@ func (a *App) shutdown(ctx context.Context) {
- The startup method is called as soon as Wails allocates the resources it needs and is a good place for creating resources,
setting up event listeners and anything else the application needs at startup.
It is given a `context.Context` which is usually saved in a struct field. This context is needed for calling the
It is given a [`context.Context`](https://pkg.go.dev/context) which is usually saved in a struct field. This context is needed for calling the
[runtime](../reference/runtime/intro.mdx). If this method returns an error, the application will terminate.
In dev mode, the error will be output to the console.
@ -55,7 +59,6 @@ func main() {
log.Fatal(err)
}
}
```
More information on application lifecycle hooks can be found [here](../howdoesitwork.mdx#application-lifecycle-callbacks).
@ -65,7 +68,12 @@ More information on application lifecycle hooks can be found [here](../howdoesit
It is likely that you will want to call Go methods from the frontend. This is normally done by adding public methods to
the already defined struct in `app.go`:
```go {16-18} title="app.go"
```go {3,21-23} title="app.go"
import (
"context"
"fmt"
)
type App struct {
ctx context.Context
}
@ -82,7 +90,7 @@ func (a *App) shutdown(ctx context.Context) {
}
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
return fmt.Sprintf("Hello %s!", name)
}
```
@ -99,15 +107,14 @@ func main() {
Height: 600,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}
```
This will bind all public methods in our `App` struct (it will never bind the startup and shutdown methods).
@ -133,10 +140,10 @@ func main() {
otherStruct.SetContext(ctx)
},
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
Bind: []interface{}{
app,
otherStruct
},
},
})
if err != nil {
log.Fatal(err)
@ -187,18 +194,17 @@ func main() {
Height: 600,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
EnumBind: []interface{}{
AllWeekdays,
},
Bind: []interface{}{
app,
},
EnumBind: []interface{}{
AllWeekdays,
},
})
if err != nil {
log.Fatal(err)
}
}
```
This will add missing enums to your `model.ts` file.
@ -223,15 +229,14 @@ func main() {
OnStartup: app.startup,
OnShutdown: app.shutdown,
Menu: app.menu(),
Bind: []interface{}{
app,
},
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}
```
## Assets

View file

@ -59,6 +59,24 @@ func main() {
}
```
If you want to handle universal links as well, follow this [guide](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) to add required entitlements, add required keys to Info.plist and configure `apple-app-site-association` on your website.
Here is example for Info.plist:
```xml
<key>NSUserActivityTypes</key>
<array>
<string>NSUserActivityTypeBrowsingWeb</string>
</array>
```
And for entitlements.plist
```xml
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myawesomeapp.com</string>
</array>
```
### Windows
On Windows Custom Protocol Schemes is supported only with NSIS installer. During installation, the installer will create a

View file

@ -63,17 +63,17 @@ func main() {
type App struct {
ctx context.Context
ctx context.Context
}
func (b *App) startup(ctx context.Context) {
b.ctx = ctx
b.ctx = ctx
}
func (b *App) shutdown(ctx context.Context) {}
func (b *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
return fmt.Sprintf("Hello %s!", name)
}
```
@ -178,18 +178,18 @@ func main() {
type App struct {
ctx context.Context
ctx context.Context
}
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
return fmt.Sprintf("Hello %s!", name)
}
```
You may bind as many structs as you like. Just make sure you create an instance of it and pass it in `Bind`:
```go {10-12}
//...
//...
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
@ -203,7 +203,6 @@ You may bind as many structs as you like. Just make sure you create an instance
&mystruct2{},
},
})
```
You may bind enums types as well.
@ -237,7 +236,7 @@ var AllWeekdays = []struct {
```
```go {10-12}
//...
//...
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
@ -250,11 +249,10 @@ var AllWeekdays = []struct {
&mystruct1{},
&mystruct2{},
},
EnumBind: []interface{}{
AllWeekdays,
},
EnumBind: []interface{}{
AllWeekdays,
},
})
```
When you run `wails dev` (or `wails generate module`), a frontend module will be generated containing the following:

View file

@ -8,10 +8,23 @@ It is possible to add an application menu to Wails projects. This is achieved by
setting it in the [`Menu`](../reference/options.mdx#menu) application config, or by calling the runtime method
[MenuSetApplicationMenu](../reference/runtime/menu.mdx#menusetapplicationmenu).
An example of how to create a menu:
An example of how to create a menu, using [the `NewApp` scaffold](../guides/application-development.mdx):
```go
```go title="main.go"
package main
import (
"log"
"runtime"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"github.com/wailsapp/wails/v2/pkg/options"
rt "github.com/wailsapp/wails/v2/pkg/runtime"
)
func main() {
app := NewApp()
AppMenu := menu.NewMenu()
@ -29,7 +42,7 @@ An example of how to create a menu:
})
if runtime.GOOS == "darwin" {
AppMenu.Append(menu.EditMenu()) // On macOS platform, EditMenu should be appended to enable Cmd+C, Cmd+V, Cmd+Z... shortcuts
AppMenu.Append(menu.EditMenu()) // On macOS platform, EditMenu should be appended to enable Cmd+C, Cmd+V, Cmd+Z... shortcuts
}
err := wails.Run(&options.App{
@ -40,8 +53,11 @@ An example of how to create a menu:
Bind: []interface{}{
app,
},
)
// ...
})
if err != nil {
log.Fatal(err)
}
}
```
It is also possible to dynamically update the menu, by updating the menu struct and calling

View file

@ -86,7 +86,7 @@ Go: `WindowSetDarkTheme(ctx context.Context)`<br/> JS: `WindowSetDarkTheme()`
Go: `WindowShow(ctx context.Context)`<br/> JS: `WindowShow()`
### WindowShow 隐藏窗口
### WindowHide 隐藏窗口
如果当前可见,则隐藏窗口。

View file

@ -86,7 +86,7 @@ Go: `WindowSetDarkTheme(ctx context.Context)`<br/> JS: `WindowSetDarkTheme()`
Go: `WindowShow(ctx context.Context)`<br/> JS: `WindowShow()`
### WindowShow 隐藏窗口
### WindowHide 隐藏窗口
如果当前可见,则隐藏窗口。

View file

@ -86,7 +86,7 @@ Go: `WindowSetDarkTheme(ctx context.Context)`<br/> JS: `WindowSetDarkTheme()`
Go: `WindowShow(ctx context.Context)`<br/> JS: `WindowShow()`
### WindowShow 隐藏窗口
### WindowHide 隐藏窗口
如果当前可见,则隐藏窗口。

View file

@ -86,7 +86,7 @@ Go: `WindowSetDarkTheme(ctx context.Context)`<br/> JS: `WindowSetDarkTheme()`
Go: `WindowShow(ctx context.Context)`<br/> JS: `WindowShow()`
### WindowShow 隐藏窗口
### WindowHide 隐藏窗口
如果当前可见,则隐藏窗口。

View file

@ -86,7 +86,7 @@ Go: `WindowSetDarkTheme(ctx context.Context)`<br/> JS: `WindowSetDarkTheme()`
Go: `WindowShow(ctx context.Context)`<br/> JS: `WindowShow()`
### WindowShow 隐藏窗口
### WindowHide 隐藏窗口
如果当前可见,则隐藏窗口。

View file

@ -86,7 +86,7 @@ Go: `WindowSetDarkTheme(ctx context.Context)`<br/> JS: `WindowSetDarkTheme()`
Go: `WindowShow(ctx context.Context)`<br/> JS: `WindowShow()`
### WindowShow 隐藏窗口
### WindowHide 隐藏窗口
如果当前可见,则隐藏窗口。

View file

@ -86,7 +86,7 @@ Go: `WindowSetDarkTheme(ctx context.Context)`<br/> JS: `WindowSetDarkTheme()`
Go: `WindowShow(ctx context.Context)`<br/> JS: `WindowShow()`
### WindowShow 隐藏窗口
### WindowHide 隐藏窗口
如果当前可见,则隐藏窗口。

View file

@ -14,6 +14,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Updated menu reference docs with complete imports by @agilgur5 in [#4727](https://github.com/wailsapp/wails/pull/4727) and [#4742](https://github.com/wailsapp/wails/pull/4742)
- Fixed menu reference syntax by @agilgur5 in [#4726](https://github.com/wailsapp/wails/pull/4726)
- Fixed indentation in Application Development guide by @agilgur5 in [#4730](https://github.com/wailsapp/wails/pull/4730)
- Updated Application Development guide to show imports in the `app.go` snippets by @agilgur5 in [#4731](https://github.com/wailsapp/wails/pull/4731)
- Fixed link to CoC in Community Guide when there was a trailing slash by @agilgur5 in [#4732](https://github.com/wailsapp/wails/pull/4732)
- Fixed indentation in "How does it work?" page by @agilgur5 in [#4733](https://github.com/wailsapp/wails/pull/4733)
- Updated wails installation documentation to allow copying the `install wails` command with one click by @tilak999 in [#4692](https://github.com/wailsapp/wails/pull/4692)
## v2.11.0 - 2025-11-08
### Added
@ -23,12 +33,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `ContentProtection` option to allow hiding the application window from screen sharing software [#4241](https://github.com/wailsapp/wails/pull/4241) by [@Taiterbase](https://github.com/Taiterbase)
- Added `build:tags` to project specification for automatically adding compilation tags by @symball in [PR](https://github.com/wailsapp/wails/pull/4439)
- Support for binding generics in [PR](https://github.dev/wailsapp/wails/pull/3626) by @ktsivkov
- Add universal link support for macOS by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4693)
### Fixed
- Added url validation for BrowserOpenURL by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4484)
- Fixed C compilation error in onWayland on Linux due to declaration after label [#4446](https://github.com/wailsapp/wails/pull/4446) by [@jaesung9507](https://github.com/jaesung9507)
- Use computed style when adding 'wails-drop-target-active' [PR](https://github.com/wailsapp/wails/pull/4420) by [@riannucci](https://github.com/riannucci)
- Fixed panic when adding menuroles on Linux [#4558](https://github.com/wailsapp/wails/pull/4558) by [@jaesung9507](https://github.com/jaesung9507)
- Fixed generated enums ordering [#4664](https://github.com/wailsapp/wails/pull/4664) by [@rprtr258](https://github.com/rprtr258).
- Fixed Discord badge in README by @sharkmu in [PR](https://github.com/wailsapp/wails/pull/4626)
- Fixed HTML DnD by @leaanthony

View file

@ -6,7 +6,7 @@ The number of Wails users is growing at an incredible rate, and if you're readin
### Code of Conduct
This [Code of Conduct](./coc) is an easy guide to develop the technical communities in which we participate.
This [Code of Conduct](/coc) is an easy guide to develop the technical communities in which we participate.
### Stay in the Know

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Before After
Before After

View file

@ -73,6 +73,10 @@ import TabItem from "@theme/TabItem";
Run `go install github.com/wailsapp/wails/v2/cmd/wails@latest` to install the Wails CLI.
```shell
go install github.com/wailsapp/wails/v2/cmd/wails@latest
```
Note: If you get an error similar to this:
```shell

View file

@ -29,6 +29,8 @@ If you are unsure about a template, inspect `package.json` and `wails.json` for
- [wails-template-naive](https://github.com/tk103331/wails-template-naive) - Wails template based on Naive UI (A Vue 3 Component Library)
- [wails-template-primevue-sakai](https://github.com/TekWizely/wails-template-primevue-sakai) - Wails starter using [PrimeVue's Sakai Application Template](https://sakai.primevue.org) (Vite, Vue, PrimeVue, TailwindCSS, Vue Router, Themes, Dark Mode, UI Components, and more)
- [wails-template-tdesign-js](https://github.com/tongque0/wails-template-tdesign-js) - Wails template based on TDesign UI (a Vue 3 UI library by Tencent), using Vite, Pinia, Vue Router, ESLint, and Prettier.
- [wails-nuxt-template](https://github.com/paulbrickwell/wails-nuxt-template) - Wails template using Nuxt 4
- [wails-nuxt-tailwind-template](https://github.com/paulbrickwell/wails-nuxt-tailwind-template) - Wails template using Nuxt 4 and the official Nuxt Tailwind and Nuxt Color Mode modules
## Angular