Repository: primefaces/primefaces (2025-07-24T14:34:29Z)

Issue:
Messages: client-side appendMessage ignores closable attribute
### Describe the bug

`closable` attribute in messages component is ignored when using the client-side `appendMessage` function

### Reproducer

Simply run `PF('myWidget').appendMessage({severity: 'info', summary: 'hello'})` - it unconditionally renders a close icon when it should not

### Expected behavior

close icon is not rendered if `closable=false` (default case)

### PrimeFaces edition

Community

### PrimeFaces version

16.0.0-SNAPSHOT

### Theme

_No response_

### JSF implementation

All

### JSF version

4.0

### Java version

21

### Browser(s)

_No response_


Discussion:

Patch:
diff --git a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Messages.java b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Messages.java
index f351e37973..bce7a2d4c5 100644
--- a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Messages.java
+++ b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Messages.java
@@ -39,6 +39,9 @@
  * Component wrapper for the PrimeFaces {@code p:messages}.
  */
 public abstract class Messages extends AbstractComponent {
+    public WebElement getCloseButton(String severity) {
+        return findElement(By.cssSelector(".ui-messages-" + severity + " > .ui-messages-close"));
+    }
 
     public boolean isEmpty() {
         if (!isDisplayed()) {
diff --git a/primefaces/src/main/frontend/packages/components/src/messages/messages.widget.ts b/primefaces/src/main/frontend/packages/components/src/messages/messages.widget.ts
index 13e9ec731f..21d1cbdc66 100644
--- a/primefaces/src/main/frontend/packages/components/src/messages/messages.widget.ts
+++ b/primefaces/src/main/frontend/packages/components/src/messages/messages.widget.ts
@@ -5,6 +5,7 @@
  * configuration is usually meant to be read-only and should not be modified.
  */
 export interface MessagesCfg extends PrimeType.widget.BaseWidgetCfg {
+    closable: boolean;
 }
 
 /**
@@ -37,9 +38,9 @@ export class Messages<Cfg extends MessagesCfg = MessagesCfg> extends PrimeFaces.
         if (severityContainer.length === 0) {
             severityContainer = this.jq.append(
                  '<div class="ui-messages-' + msg.severity + '">' +
-                    '<a href="#" class="ui-messages-close" onclick="$(this).parent().slideUp();return false;" role="button" aria-label="'+closeLabel+'">' +
+                    (this.cfg.closable ? '<a href="#" class="ui-messages-close" onclick="$(this).parent().slideUp();return false;" role="button" aria-label="' + closeLabel + '">' +
                         '<span class="ui-icon ui-icon-close"></span>' +
-                    '</a>' +
+                    '</a>' : '') +
                     '<span class="ui-messages-' + msg.severity + '-icon"></span>' +
                     '<ul>' +
 
diff --git a/primefaces/src/main/java/org/primefaces/component/messages/MessagesRenderer.java b/primefaces/src/main/java/org/primefaces/component/messages/MessagesRenderer.java
index b5985c6102..d379397fe4 100644
--- a/primefaces/src/main/java/org/primefaces/component/messages/MessagesRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/messages/MessagesRenderer.java
@@ -110,6 +110,7 @@ else if (severity.equals(FacesMessage.SEVERITY_FATAL)) {
     protected void encodeScript(FacesContext context, Messages component) throws IOException {
         WidgetBuilder wb = getWidgetBuilder(context);
         wb.init("Messages", component)
+                .attr("closable", component.isClosable())
                 .finish();
     }
 


Test patch:
diff --git a/primefaces-integration-tests/src/main/webapp/messages/messages002.xhtml b/primefaces-integration-tests/src/main/webapp/messages/messages002.xhtml
new file mode 100644
index 0000000000..96e81c43e6
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/messages/messages002.xhtml
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="jakarta.faces.html"
+      xmlns:f="jakarta.faces.core"
+      xmlns:p="primefaces">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+        <h:form id="form">
+            <p:messages id="messages" widgetVar="messages">
+                <p:autoUpdate/>
+            </p:messages>
+
+            <p:messages id="messagesClosable" widgetVar="messagesClosable" closable="true">
+                <p:autoUpdate/>
+            </p:messages>
+        </h:form>
+    </h:body>
+</f:view>
+
+</html>
\ No newline at end of file
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/messages/Messages002Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/messages/Messages002Test.java
new file mode 100644
index 0000000000..c44a75e97f
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/messages/Messages002Test.java
@@ -0,0 +1,67 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2025 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.messages;
+
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.AbstractPrimePageTest;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.Messages;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.support.FindBy;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class Messages002Test extends AbstractPrimePageTest {
+    @Test
+    @DisplayName("client-side appendMessage: close icon not rendered")
+    public void testNotClosable(Page page) {
+        PrimeSelenium.executeScript("PF('messages').appendMessage({severity:'info', summary:'Info'});");
+
+        assertThrows(NoSuchElementException.class, () -> page.messages.getCloseButton("info"));
+    }
+
+    @Test
+    @DisplayName("client-side appendMessage: close icon rendered")
+    public void testClosable(Page page) {
+        PrimeSelenium.executeScript("PF('messagesClosable').appendMessage({severity:'info', summary:'Info'});");
+
+        assertDisplayed(page.messagesClosable.getCloseButton("info"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:messages")
+        Messages messages;
+
+        @FindBy(id = "form:messagesClosable")
+        Messages messagesClosable;
+
+        @Override
+        public String getLocation() {
+            return "messages/messages002.xhtml";
+        }
+    }
+}