diff --git a/.stylelintrc.mjs b/.stylelintrc.mjs
index d1376c798d..d7e57e064c 100644
--- a/.stylelintrc.mjs
+++ b/.stylelintrc.mjs
@@ -12,7 +12,10 @@ export default {
rules: {
// Disallow relative font units since we don't know the base font size in other apps
'unit-disallowed-list': ['rem', 'em'],
- 'order/properties-alphabetical-order': true,
+ 'order/properties-order': [
+ ['all'],
+ { 'unspecified': 'bottomAlphabetical' },
+ ],
'@kong/design-tokens/use-proper-token': true,
'@stylistic/indentation': [2, { baseIndentLevel: 0 }],
// Only allow @kong/design-tokens or `--kong-ui-*` CSS custom properties
diff --git a/docs/components/button.md b/docs/components/button.md
index c6fa9d94e0..056a42d808 100644
--- a/docs/components/button.md
+++ b/docs/components/button.md
@@ -13,18 +13,26 @@ and configuration options.
### appearance
-The Button component can take 1 of 4 appearance values:
+The Button component can take 1 of 5 appearance values:
- `primary`
- `secondary`
- `tertiary`
- `danger`
+- `none`
+
+:::tip NOTE
+Use `appearance="none"` to get an unstyled button, making it easier to customize from scratch. This removes the built-in styling but retains the button's functionality.
+
+When `appearance="none"` is set, the `size` prop only works if `icon` is `true`; otherwise, `size` has no effect.
+:::
Primary
Secondary
Tertiary
Danger
+ None
```html
@@ -32,6 +40,7 @@ The Button component can take 1 of 4 appearance values:
Secondary
Tertiary
Danger
+None
```
### size
diff --git a/docs/vite.config.ts b/docs/vite.config.ts
index e9f3eedd97..b0dcf72a18 100644
--- a/docs/vite.config.ts
+++ b/docs/vite.config.ts
@@ -26,6 +26,7 @@ export default defineConfig({
css: {
preprocessorOptions: {
scss: {
+ api: 'modern',
// Inject the @kong/design-tokens SCSS variables, kongponents variables and mixins to make them available for all components.
// This is not needed in host applications.
additionalData: `
diff --git a/sandbox/pages/SandboxButton.vue b/sandbox/pages/SandboxButton.vue
index 847b0a1c84..045e910236 100644
--- a/sandbox/pages/SandboxButton.vue
+++ b/sandbox/pages/SandboxButton.vue
@@ -13,7 +13,7 @@
title="appearance"
>
- KButton component takes 1 of 4 appearance values:
+ KButton component takes 1 of 5 appearance values:
-
primary - default value
@@ -27,6 +27,9 @@
-
danger
+ -
+ none
+
@@ -51,6 +54,12 @@
>
Danger
+
+ None
+
Disabled
+
+ Disabled
+
+
Still Primary
@@ -188,6 +205,13 @@
>
Danger
+
+ None
+
Danger
+
+ None
+
+
+ None
+
+
@@ -365,6 +403,10 @@
Danger
+
+ None
+
+
@@ -392,6 +434,13 @@
Danger
+
+ None
+
+
@@ -429,6 +478,13 @@
>
+
+
+
@@ -452,6 +508,12 @@
>
+
+
+
+
+
+
+
+
+ Custom
+
+
+ Custom
+
@@ -507,4 +599,23 @@ const test = () => {
gap: $kui-space-50;
}
}
+
+/* Low-specificity styles should be able to override */
+.custom-button {
+ background-color: #42b883;
+ border-radius: 8px;
+ color: #fff;
+ font-size: 16px;
+ font-weight: 600;
+ padding: 8px 16px;
+
+ &:hover:not(:disabled) {
+ background-color: #33a06f;
+ transition-duration: .2s;
+ }
+
+ &:disabled {
+ opacity: 0.3;
+ }
+}
diff --git a/src/components/KButton/KButton.vue b/src/components/KButton/KButton.vue
index a8cb98194f..7120fe3356 100644
--- a/src/components/KButton/KButton.vue
+++ b/src/components/KButton/KButton.vue
@@ -2,10 +2,12 @@
@@ -86,6 +88,24 @@ const buttonType = computed((): string => {
return 'button'
})
+const buttonAppearance = computed((): ButtonAppearance | [ButtonAppearance, string] => {
+ // If the appearance is invalid, output both to keep backwards compatibility
+ // in case some of the tests rely on the invalid appearance output
+ if (Object.values(ButtonAppearances).indexOf(props.appearance) === -1) {
+ return ['primary', props.appearance]
+ }
+
+ return props.appearance
+})
+
+const buttonSize = computed((): ButtonSize | null => {
+ if (props.appearance === 'none' && !props.icon) {
+ return null
+ }
+
+ return props.size
+})
+
/**
* Strips falsy `disabled` attribute, so it does not fall onto native elements.
* Vue 3 no longer removes attribute if the value is boolean false. Instead, it's set as attr="false".
@@ -95,10 +115,14 @@ const buttonType = computed((): string => {
const strippedAttrs = computed((): typeof attrs => {
const modifiedAttrs = Object.assign({}, attrs)
- if (props.to && typeof props.to === 'string') {
- modifiedAttrs.href = props.to
- } else if (props.to) {
- modifiedAttrs.to = props.to
+ if (props.disabled) {
+ modifiedAttrs.href = null
+ } else {
+ if (props.to && typeof props.to === 'string') {
+ modifiedAttrs.href = props.to
+ } else if (props.to) {
+ modifiedAttrs.to = props.to
+ }
}
if (props.disabled !== undefined && props.disabled !== false) {
@@ -110,6 +134,25 @@ const strippedAttrs = computed((): typeof attrs => {
return modifiedAttrs
})
+const stop = (event: Event) => {
+ event.preventDefault()
+ event.stopPropagation()
+}
+
+const listeners = computed(() => {
+ if (!props.disabled || buttonType.value === 'button') {
+ return {}
+ }
+
+ // Only prevent clicks on disabled links, emulating the same behavior as a disabled button
+ return {
+ clickCapture: stop,
+ dblclickCapture: stop,
+ mousedownCapture: stop,
+ mouseupCapture: stop,
+ }
+})
+
onMounted(() => {
if (slots.icon) {
console.warn('KButton: `icon` slot is deprecated. Please slot an icon into the `default` slot instead. See the migration guide for more details: https://kongponents.konghq.com/guide/migrating-to-version-9.html#kbutton')
@@ -124,36 +167,9 @@ export default {