Chuyển Đổi HTML Sang PDF Trên Trình Duyệt: Khi Lập Trình Viên Đau Đầu

Chuyển Đổi HTML Sang PDF Trên Trình Duyệt: Khi Lập Trình Viên Đau Đầu

Chuyển đổi HTML và CSS sang PDF ngay trên trình duyệt đầy rẫy những thách thức bất ngờ, từ việc thiếu hỗ trợ CSS đến hình ảnh mờ và lỗi ngắt trang. Bài viết này sẽ đi sâu vào lý do tại sao việc tạo PDF từ HTML trong trình duyệt lại khó khăn đến vậy, khám phá các thư viện JavaScript phổ biến như html2pdf.js, jsPDF, pdfmake và gợi ý khi nào nên cân nhắc giải pháp lai hoặc phía máy chủ để có kết quả tốt hơn.

Lê Lân profile pictureLê Lân
Bản Tin Unicorn Club: "Món Ngon" Công Nghệ Tuần Này

Bản Tin Unicorn Club: "Món Ngon" Công Nghệ Tuần Này

Chào mừng bạn đến với bản tin Unicorn Club! Tuần này chúng ta sẽ cùng "nghía" qua những "món ngon" về UX, tối ưu hiệu suất, những "phép thuật" CSS đỉnh cao, và cả những bài học quý giá về Accessibility và Product Design. Đừng bỏ lỡ cơ hội nâng cấp kiến thức và kỹ năng của bạn nhé!

Lê Lân profile pictureLê Lân
Giải mã bí ẩn: Tại sao hệ thống thiết kế 'chết yểu' sau năm thứ hai?

Giải mã bí ẩn: Tại sao hệ thống thiết kế 'chết yểu' sau năm thứ hai?

Bạn có bao giờ tự hỏi: tại sao một hệ thống thiết kế (Design System) hoành tráng lại thường "chết yểu" chỉ sau 2 năm hoạt động? Làm sao để tránh được những cơn ác mộng mang tên "prop explosion" (bùng nổ thuộc tính), sự hỗn loạn về theme, và hàng tá vấn đề bảo trì khiến cả đội ngũ phát triển "phát điên"? Hôm nay, chúng ta sẽ cùng nhau giải mã bí ẩn này và trang bị những "bí kíp sinh tồn" để hệ thống thiết kế của bạn không chỉ tồn tại mà còn phát triển mạnh mẽ!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/design_system_lifecycle.png' alt='Vòng đời của một hệ thống thiết kế'>I. Giấc Mơ Tuyệt Vời... và Cơn Ác Mộng Đáng SợMỗi đội phát triển, dù lớn hay nhỏ, đều sẽ có lúc thốt lên: "Chúng ta cần một hệ thống thiết kế!" Ban đầu, mọi thứ cứ như một giấc mơ: các thành phần (component) đồng nhất, tốc độ tạo prototype nhanh chóng, ít lỗi thiết kế hơn hẳn. Cảm giác như được giải phóng khỏi hàng tá công việc lặp đi lặp lại vậy!Thế nhưng, "giấc mơ" ấy nhanh chóng lộ những vết nứt. Cái nút `Button` sáng bóng ngày nào giờ đây cần tới... mười hai thuộc tính (props) khác nhau để điều khiển. Một nửa team thì dùng các biến thể (variants) trên Figma mà chẳng tồn tại trong code. Đổi một màu sắc thôi mà bốn tính năng khác "banh chành". Thêm chế độ tối (dark mode) ư? Cứ như phải viết lại cả ứng dụng vậy!Thứ ban đầu được coi là "người hùng" để thống nhất mọi thứ, lại biến thành một mớ hỗn độn dễ vỡ, phình to và quá trừu tượng. Bài viết này chính là cẩm nang sinh tồn của bạn: làm sao để xây dựng một hệ thống thiết kế không tự sụp đổ dưới sức nặng của chính nó!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/broken_design_system.png' alt='Hệ thống thiết kế hỗn loạn'>II. Những Dấu Hiệu Cảnh Báo "Sụp Đổ Sớm"Bạn có thấy các dấu hiệu dưới đây không? Cảnh báo! Hệ thống thiết kế của bạn đang có nguy cơ gặp "đại họa" đó!Prop Explosion (Bùng nổ thuộc tính): Nhìn dòng code này mà xem: `<Button variant="outline" size="sm" iconLeft="check" iconRight="x" loading primary secondary destructive subtle />`. Ui chao, một cái nút mà như một "cây thông Noel" đầy đủ mọi loại "phụ kiện"! Việc này khiến component trở nên khó hiểu, khó dùng và cực kỳ khó bảo trì.Figma vs. Storybook vs. Thực Tế: Đây là "tam giác quỷ" khiến cả designer và developer "lạc lối". Ai cũng tự hỏi: đâu mới là nguồn chân lý? Thiết kế trên Figma, tài liệu trên Storybook hay cái đang chạy trên ứng dụng? Khi chúng không khớp nhau, thì mọi thứ đều sai!Token Fragmentation (Phân mảnh token): Bạn có `padding-sm`, `spacing.small`, và `theme.space.s` cùng tồn tại không? Giống như mỗi người gọi tên "màu xanh" một kiểu vậy, dẫn đến sự không nhất quán và khó kiểm soát.Theme Update Hell (Địa ngục cập nhật theme): Chỉ cần đổi một token nhỏ thôi mà 15 component khác "biểu tình" đòi chỉnh sửa. Cứ như thay một bóng đèn mà cả ngôi nhà mất điện vậy!"Nếu hệ thống thiết kế của bạn cần một khóa học onboarding (hướng dẫn sử dụng) dài cả tuần, thì có lẽ nó đã quá phức tạp rồi đó!"<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/prop_explosion_button.png' alt='Button với quá nhiều prop'>III. Bí Kíp Sinh Tồn Cho Hệ Thống Thiết Kế Của BạnĐừng lo lắng! Đây là những quy tắc vàng, là "bảo bối" giúp hệ thống thiết kế của bạn thoát khỏi lời nguyền "sớm nở tối tàn":1. Khởi Đầu Từ Token, Đừng Từ Component!Nghe có vẻ ngược đời, nhưng tin tôi đi, đây là bước đi cực kỳ thông minh! Trước khi bạn bắt tay vào xây dựng bất kỳ component nào, hãy định nghĩa các **design token** của mình trước đã. Design token giống như "ADN" của hệ thống thiết kế vậy – chúng là những quyết định thiết kế nhỏ nhất, cơ bản nhất:Màu sắc (Color)Khoảng cách (Spacing)Kiểu chữ (Typography)Độ nâng (Elevation)Độ cong viền (Border radii)Việc tập trung hóa các token này sẽ giúp việc tùy biến theme dễ dàng và nhanh chóng hơn rất nhiều. Hãy dùng các công cụ mạnh mẽ như <a href="https://amzn.github.io/style-dictionary/">Style Dictionary</a> (có cả kho báu trên GitHub của Amazon đó!), hay các "chiến hữu" khác như <a href="https://github.com/salesforce-ux/theo">Theo</a>, <a href="https://tokens.studio/">Token Studio</a>, <a href="https://github.com/design-tokens/community-group">Design Tokens CLI</a>. Hoặc đơn giản là một file JSON/YAML "truyền thống" cũng được. Nếu có thể, hãy đồng bộ chúng với Figma nữa nhé!Đây là một ví dụ đơn giản về cấu trúc token, bao gồm cả token cơ bản (base) và token ngữ nghĩa (semantic):```json{ "color": { "base": { "blue500": { "value": "#0055ff" }, "gray100": { "value": "#f5f5f5" } }, "semantic": { "primary": { "value": "{color.base.blue500}" }, "background": { "value": "{color.base.gray100}" }, "buttonBackground": { "value": "{color.semantic.primary}" } } }, "spacing": { "s": { "value": "8px" }, "m": { "value": "16px" } }}```Style Dictionary sẽ "biến hóa" những token này thành các định dạng phù hợp với từng nền tảng (biến CSS, SCSS, JS, v.v.), giúp bạn cập nhật các token ngữ nghĩa trên toàn cầu mà không cần phải "đụng chạm" đến từng chỗ sử dụng. Tuyệt vời phải không nào?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/design_tokens_concept.png' alt='Thiết kế dựa trên token'>2. Ưu Tiên Composition (Ghép Nối) Hơn Configuration (Cấu Hình)Hãy tránh xa cái bẫy "mega-component" – tức là một component khổng lồ hỗ trợ hàng tá prop! Thay vì tạo ra một cái `<Card>` mà hỗ trợ tới 20 prop khác nhau, hãy nghĩ đến việc dùng "slots" và các component con. Giống như bạn lắp ráp LEGO vậy, mỗi miếng LEGO chỉ làm một việc đơn giản, nhưng khi ghép lại thì tạo ra cả một lâu đài!Cùng xem sự khác biệt "một trời một vực" này nhé:Phong cách "Prop Explosion" (Hỗn Loạn Prop):```html<Card heading="Chào mừng bạn" content="Đây là một Card được cấu hình." footerAction={<Button>Gửi</Button>} />```(Một `<Card>` cố gắng nhồi nhét mọi thứ vào các thuộc tính của nó.)Phong cách Composition (Ghép Nối):```html<Card> <CardHeader> <Heading level={3}>Chào mừng bạn</Heading> </CardHeader> <CardContent> <Text>Đây là một component Card được xây dựng bằng cách ghép nối.</Text> </CardContent> <CardFooter> <Button variant="primary">Gửi</Button> </CardFooter></Card>```(Mỗi phần của `<Card>` là một component riêng biệt, có thể tùy biến linh hoạt.)Ghép nối giúp code rõ ràng hơn, linh hoạt hơn và dễ bảo trì về lâu dài. "Đừng cố gắng biến một con voi thành một con chuột bằng cách nhét nó vào một cái hộp!"<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/composition_vs_config.png' alt='Ưu tiên composition hơn configuration'>3. Kẻ Vạch Rõ Ràng Giữa Design System và Lớp Ứng DụngHãy nhớ điều này: hệ thống thiết kế của bạn không nên biết bất kỳ logic nghiệp vụ nào của ứng dụng! Giống như một đầu bếp giỏi chỉ lo nấu ăn, chứ không cần biết công ty đang bán món gì lãi nhất vậy.Ví dụ "Sai bét nhè":```html<Button isUserAdmin={user.role === 'admin'} />```(Nút `Button` đang "quan tâm" đến vai trò của người dùng - việc của ứng dụng!)Ví dụ "Chuẩn không cần chỉnh":```html{user.role === 'admin' && <Button>Xóa</Button>}```(Logic kiểm tra quyền hạn nằm ở lớp ứng dụng, chỉ khi đủ điều kiện thì mới render nút.)Hãy giữ cho các component của bạn "thuần khiết" và chỉ tập trung vào giao diện, với các API dễ ghép nối. Điều này giúp hệ thống thiết kế độc lập và tái sử dụng cao.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/separation_of_concerns.png' alt='Tách biệt Design System và lớp ứng dụng'>4. Kiểm Thử Như Một Sản Phẩm (và Vượt Qua Áp Lực!)Coi hệ thống thiết kế của bạn như một sản phẩm! Có sản phẩm thì phải có kiểm thử, đúng không nào?Kiểm thử hồi quy giao diện (Visual Regression Tests): Dùng các "thám tử" tự động như Chromatic, Percy, hay Loki để đảm bảo không có bất kỳ thay đổi hình ảnh ngoài ý muốn nào. Cứ như có một người giám sát "soi" từng pixel vậy!Kiểm tra khả năng tiếp cận (Accessibility Checks) trong CI: Đảm bảo hệ thống của bạn thân thiện với mọi người, kể cả người khuyết tật. Các công cụ như axe-core, Lighthouse sẽ là "đôi mắt" giúp bạn nhìn thấy những điểm chưa ổn.Yêu cầu đội ngũ thiết kế ký duyệt (signoff) trên Storybook previews: Đảm bảo giữa thiết kế và code luôn có sự đồng điệu. "Mắt thấy, tai nghe, tay chạm" trước khi mọi thứ ra lò!Kiểm thử hiệu năng của các component dùng chung dưới tải thực tế:Render 1.000+ hàng trong bảng.Thay đổi kích thước bố cục lưới phản hồi (responsive grid layouts).Tái sắp xếp nội dung trên các trang nặng với nhiều loại phương tiện.Hãy thiết lập các tiêu chuẩn hiệu suất cơ bản:Thời gian render ban đầu dưới 200ms.Độ trễ tương tác dưới 100ms.Cumulative Layout Shift (CLS) dưới 0.1 (đừng để trang nhảy nhót khi người dùng đang xem).Dùng các công cụ như Lighthouse, WebPageTest, hoặc các chỉ số RUM để theo dõi những ngưỡng này trong môi trường sản xuất. Bạn không chỉ kiểm thử code – bạn đang kiểm thử những kỳ vọng! Khả năng tiếp cận không chỉ là về tỷ lệ tương phản – hãy xác minh trạng thái focus, điều hướng bằng bàn phím và nhãn cho trình đọc màn hình. Nếu hệ thống thiết kế của bạn được sử dụng trên toàn cầu, hãy lên kế hoạch cho quốc tế hóa: bố cục RTL (viết từ phải sang trái), độ dài nội dung động và các trạng thái UI đã dịch.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/design_system_testing.png' alt='Kiểm thử hệ thống thiết kế'>5. Cắt Tỉa Mạnh Tay (Prune Aggressively!)Một hệ thống thiết kế sẽ "mục ruỗng" nếu bạn cứ giữ mọi thứ mãi mãi! Giống như khu vườn của bạn vậy, nếu không cắt tỉa, nó sẽ trở nên rậm rạp và khó quản lý.Loại bỏ (deprecate) các prop và variant không dùng đến: Nếu không ai xài, cứ mạnh dạn cho nó "nghỉ hưu" đi!Duy trì "điểm sức khỏe" của component: Theo dõi tần suất sử dụng, lần cuối cập nhật, số lượng lỗi...Xem xét việc sử dụng hàng quý: Nếu chẳng ai dùng `Tag.variant="holographicRainbow"` thì... biến nó đi! Đừng ngần ngại "dọn dẹp" những thứ không cần thiết.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/pruning_design_system.png' alt='Cắt tỉa hệ thống thiết kế'>6. Gán Quyền Sở Hữu, Quy Trình Quản Lý và Phiên Bản HóaMột hệ thống thiết kế cần có "ông chủ" rõ ràng! Hãy thành lập một nhóm nòng cốt nhỏ – gồm các designer, developer, và có thể là một product lead – những người sẽ xem xét các đề xuất, duy trì các tiêu chuẩn và giải quyết xung đột. Không có "người gác cổng" tận tâm, thì "sự hỗn loạn" sẽ thắng thế.Một cách hiệu quả để cấu trúc việc ra quyết định là thông qua quy trình RFC (Request for Comments - Yêu cầu Góp ý) nhẹ nhàng:Bất kỳ ai đề xuất thay đổi đều viết một file Markdown ngắn gọn, trình bày rõ lý do, phác thảo triển khai, tác động trực quan và kế hoạch di chuyển.Nhóm nòng cốt sẽ xem xét các RFC hàng tuần hoặc hai tuần một lần, sau đó phê duyệt, từ chối hoặc yêu cầu chỉnh sửa.Các thay đổi đã được phê duyệt sẽ được ghi vào changelog và thông báo qua Slack, Confluence hoặc bản tin về hệ thống thiết kế.Quy trình này mang lại sự minh bạch, cấu trúc và một nhịp độ phát triển nhất quán – mà không làm tắc nghẽn tiến độ.Đây là một ví dụ về RFC:```markdown# RFC: Thêm component mới `Tooltip`## Tóm tắtTạo một component cơ bản `Tooltip` hỗ trợ gợi ý khi di chuột (hover) và focus tuân thủ khả năng tiếp cận.## Lý do thiết kếCác Tooltip hiện tại đang không nhất quán và không thân thiện với khả năng tiếp cận giữa các đội. Component này sẽ bọc Popper.js và đảm bảo tuân thủ.## API đề xuất<Tooltip content="Thông tin hữu ích"> <Button>Di chuột vào tôi</Button></Tooltip>## Chiến lược di chuyểnKhông cần di chuyển. Là một cải tiến tùy chọn.## Thời gian xem xétMở để lấy ý kiến phản hồi cho đến thứ Sáu tuần sau.```Mỗi RFC sẽ được lưu trữ trong một thư mục được kiểm soát phiên bản (ví dụ: `/design-system/rfcs`) và được xem xét hàng tuần.Để giảm thiểu sự gián đoạn khi thực hiện các thay đổi gây phá vỡ (breaking changes), hãy sử dụng phiên bản hóa ngữ nghĩa (semantic versioning) và changelog. Đánh dấu các thay đổi lớn bằng `v2.0.0`, và ghi lại lộ trình di chuyển trong một file `MIGRATIONS.md` riêng. Đối với các component hoặc token được sử dụng rộng rãi, hãy cân nhắc phát hành codemods (công cụ tự động sửa code), hướng dẫn di chuyển kèm theo hình ảnh so sánh, và thiết lập cảnh báo bỏ dùng (deprecation warnings) trong CI hoặc console log để quá trình chuyển đổi diễn ra suôn sẻ hơn.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/design_system_governance.png' alt='Quản lý hệ thống thiết kế'>7. Khuyến Khích Áp Dụng Bằng Thiết Kế (Encourage Adoption by Design)Hệ thống tốt nhất là hệ thống mà mọi người thực sự sử dụng! Đừng bắt buộc, hãy khuyến khích!Cung cấp tài liệu rõ ràng: Ai đó muốn dùng thì phải biết dùng như thế nào chứ!Onboarding dễ dàng: "Nhập môn" phải thật đơn giản, ai cũng có thể bắt đầu ngay.Kênh hỗ trợ: Khi có vấn đề, phải có chỗ để hỏi, để được giúp đỡ.Và quan trọng nhất: VOTE THẮNG LỢI! Hãy thường xuyên khoe những thành quả: "Trang này được đưa lên chỉ trong nửa thời gian vì chúng ta đã dùng hệ thống thiết kế đó!" Việc áp dụng là một văn hóa, chứ không phải một mệnh lệnh.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/design_system_adoption.png' alt='Khuyến khích áp dụng hệ thống thiết kế'>IV. Di Chuyển Trong Thực Tế: Một Hành Trình "Lột Xác" Có ThậtTại một công ty SaaS cỡ trung, hệ thống thiết kế ban đầu đã "phình to" lên hơn 130 component, nhiều trong số đó chỉ dùng một lần duy nhất. Các token thì bị trùng lặp khắp nơi, và Figma thường xuyên không khớp với cái đang chạy trong môi trường sản xuất.Để thực hiện cuộc "đại di chuyển" này, đội ngũ đã làm những gì?Kiểm toán việc sử dụng component: Xác định các thành phần có giá trị cao, bị trùng lặp và những cái đã cũ.Định nghĩa một "nguồn chân lý" duy nhất cho token: Và đồng bộ nó với Figma và code bằng cách dùng Tokens Studio và Style Dictionary.Áp dụng quy tắc "chỉ dùng hệ thống mới": Tất cả các tính năng mới đều phải sử dụng thư viện đã được xây dựng lại.Áp dụng các mẫu hình ghép nối (composition patterns): Để giảm "prop explosion", đặc biệt là trong các component bố cục và card.Dùng codemods và cờ di chuyển dần dần: Để chuyển đổi các trang cũ một cách tăng dần.Những "hố đen" cần tránh:Không loại bỏ rõ ràng các component cũ: Dẫn đến việc có tới hai hệ thống cùng tồn tại, gây nhầm lẫn.Không đồng bộ thời gian biểu giữa thiết kế và kỹ thuật: Dẫn đến tắc nghẽn và chậm trễ.Di chuyển mà không có điểm chuẩn hiệu suất: Khiến việc phát hiện các hồi quy (regression) trở nên khó khăn.Sau hơn ba tháng, họ đã giảm số lượng component đi 40%, thu gọn kích thước gói (bundle size) đi 22%, và có thể triển khai chế độ tối (dark mode) chỉ với ba dòng ghi đè token! Thật đáng nể phải không?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/design_system_migration.png' alt='Di chuyển hệ thống thiết kế trong thực tế'>V. Bonus: Figma Drift (Lệch Figma) và Cách Khắc PhụcNgay cả một hệ thống vững chắc cũng có thể "sụp đổ" nếu thiết kế và code lệch pha! Giống như bạn có một bản đồ xịn nhưng thực tế lại khác vậy.Các chiến lược để "bẻ lái" lại:Sử dụng plugin token của Figma (như Tokens Studio): Để đồng bộ code và thiết kế.Hạn chế người có thể tạo biến thể Figma mới: Đảm bảo mọi thứ có kiểm soát.Tổ chức "buổi đồng bộ hệ thống thiết kế" cứ 4-6 tuần một lần giữa design/dev: Đây là dịp để "gặp gỡ và hòa giải" mọi sự khác biệt.Đồng bộ các biến thể Figma với các trạng thái component trong code: (loading, focus, error).Thường xuyên kiểm toán sự đồng nhất giữa Figma và Storybook.Việc này không đòi hỏi một đội ngũ "cầu nối" làm việc toàn thời gian – nhưng nó đòi hỏi sự kỷ luật chung. Hãy nhớ: nguồn chân lý của bạn không phải là code hay Figma. Mà là **mối quan hệ** giữa chúng!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/figma_code_drift.png' alt='Figma Drift và cách khắc phục'>VI. Thành Quả Ngọt Ngào (The Payoff!)Một hệ thống thiết kế gọn nhẹ, được bảo trì tốt sẽ "hái ra tiền" mỗi khi ai đó bắt đầu một trang hoặc một tính năng mới:Không còn phải "chỉnh từng pixel" nữa!Không còn phải "đoán mò" nữa!Không còn phải "viết lại mọi thứ" mỗi quý nữa!"Hệ thống thiết kế tốt nhất không phải là phức tạp. Nó phải rõ ràng!" Hãy xây dựng hệ thống của bạn để nó tồn tại mãi mãi!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/happy_design_system_team.png' alt='Thành quả của một hệ thống thiết kế tốt'>

Lê Lân profile pictureLê Lân
Tái Tạo Hiệu Ứng Kính Lỏng (Liquid Glass) Của Apple Với React, CSS & SVG Filters

Tái Tạo Hiệu Ứng Kính Lỏng (Liquid Glass) Của Apple Với React, CSS & SVG Filters

Chào bạn! Bạn có bao giờ trầm trồ trước hiệu ứng "Liquid Glass" (Kính lỏng) siêu mượt mà của Apple chưa? Nó khiến các yếu tố giao diện trông như thể được làm từ kính thật vậy, ảo diệu vô cùng! Mình đã "mổ xẻ" để tái tạo lại hiệu ứng này, và hóa ra, bí quyết cốt lõi lại là: làm cho văn bản nền "dịch chuyển" một cách thật chính xác! Nghe thì có vẻ đơn giản - chỉ cần dùng `backdropFilter` là xong, đúng không? Nhưng đây mới là "cú lừa": làm sao để hiệu ứng "méo mó" của filter trông y chang sự biến dạng của kính thật mà Apple đã làm, chứ không phải chỉ là làm mờ hay chỉnh sáng? Khó nhằn đấy!

Lê Lân profile pictureLê Lân
SEO Kỹ Thuật: Hậu Trường Đằng Sau Một Website TOP Google

SEO Kỹ Thuật: Hậu Trường Đằng Sau Một Website TOP Google

Bạn đã bao giờ thắc mắc tại sao website của mình có nội dung hay mà vẫn không lên top? Bí mật nằm ở SEO kỹ thuật! Cùng tìm hiểu cách tối ưu cấu trúc website, tốc độ tải trang, và khả năng thu thập thông tin của Google để website của bạn bay cao trên bảng xếp hạng tìm kiếm.

Lê Lân profile pictureLê Lân
CSS Nesting: Đã Đến Lúc “Bye Bye” SCSS/SASS Rồi Sao?

CSS Nesting: Đã Đến Lúc “Bye Bye” SCSS/SASS Rồi Sao?

Bạn đã sẵn sàng tạm biệt SCSS/SASS và chào đón CSS Nesting chưa? Khám phá cách tính năng lồng ghép CSS "chuẩn cơm mẹ nấu" giúp bạn viết code gọn gàng, dễ đọc và hiệu quả hơn, cùng những ví dụ thực tế và so sánh chi tiết với các bộ tiền xử lý quen thuộc!

Lê Lân profile pictureLê Lân
Design System: Bí Kíp Vàng Cho Frontend Developer Để Code Xịn, UI Đẹp!

Design System: Bí Kíp Vàng Cho Frontend Developer Để Code Xịn, UI Đẹp!

Trong thế giới phát triển phần mềm tốc độ cao, Design System là chìa khóa để đạt được sự nhất quán và khả năng mở rộng. Bài viết này giải thích Design System là gì, tầm quan trọng của nó với các Frontend Developer, và cách xây dựng một hệ thống cơ bản với Tailwind CSS, Figma, và Storybook. Khám phá các công cụ, quy trình từng bước, và những sai lầm cần tránh để tạo ra giao diện người dùng hiệu quả và mạch lạc.

Lê Lân profile pictureLê Lân
Shadcn/ui: Thư Viện Component Hay Triết Lý Mới?

Shadcn/ui: Thư Viện Component Hay Triết Lý Mới?

Trong thế giới frontend phát triển chóng mặt, công cụ mới cứ 'mọc như nấm sau mưa'. Nhưng thỉnh thoảng, lại có một 'siêu phẩm' xuất hiện, không chỉ mang đến tính năng mới mà còn là cả một triết lý hoàn toàn khác biệt. Và Shadcn/ui chính là một 'hiện tượng' như thế, đang làm mưa làm gió trong cộng đồng React. Nếu bạn nghe giới dev rỉ tai nhau về nó mà vẫn chưa hiểu 'điều thần kỳ' gì đã khiến nó đặc biệt đến vậy, thì bạn đã đến đúng nơi rồi đó! Bởi vì Shadcn/ui không phải là một thư viện component thông thường – mà nói đúng hơn, nó CHẲNG PHẢI LÀ THƯ VIỆN GÌ CẢ! Nghe lạ đúng không? Giờ thì, hãy cùng 'mổ xẻ' xem Shadcn/ui là cái gì, tại sao nó lại khác biệt và liệu có phải là 'chân ái' cho dự án tiếp theo của bạn không nhé! Bạn nhớ các thư viện component truyền thống như Material-UI hay Chakra UI chứ? Cách dùng thường là bạn cài đặt chúng qua npm, sau đó import các component vào rồi tùy chỉnh bằng đủ loại props và theme providers phức tạp. Kiểu như này nè: // 'Ngày xửa ngày xưa': import { Button } from '@mui/material'; function MyApp() { return <Button variant='contained'>Click Me</Button>;} Cách này ổn thôi, nhưng thường đi kèm với vài 'phiền toái' nhỏ: 'Đóng hộp' và bí ẩn: Logic và style bên trong component bị giấu tít trong node_modules. Bạn muốn biết nó hoạt động thế nào ư? Hên xui! Đau đầu với tùy chỉnh: Muốn ghi đè style ư? Coi chừng 'choảng' nhau với độ ưu tiên CSS, phải dùng !important hay vật lộn với mấy cái object theme 'khó nhằn'. Tải nặng ứng dụng: Đôi khi bạn import cả tấn code mà lại chẳng dùng đến bao nhiêu, làm ứng dụng nặng nề hơn. Shadcn/ui thì sao? Nó 'lật kèo' toàn bộ mô hình này! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ShadcnFlipModel.png' alt='Shadcn/ui lật ngược mô hình thư viện'> Nó KHÔNG phải là một npm package. Bạn không cài nó như một dependency! Thay vào đó, Shadcn/ui là một 'bộ sưu tập' các component được thiết kế đẹp mắt, có thể tái sử dụng, mà bạn sẽ 'copy-paste' thẳng vào dự án của mình bằng một công cụ dòng lệnh (CLI). <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ShadcnCliCopy.png' alt='Copy-paste qua CLI'> Khi bạn chạy lệnh kiểu như này: npx shadcn-ui@latest add button Bạn KHÔNG hề thêm một dependency mới vào package.json đâu nhé. Thay vào đó, CLI sẽ tạo ra một file button.tsx bên trong thư mục components/ui của dự án bạn. Và từ giây phút đó, đoạn code đó là CỦA BẠN! Các component này được xây dựng dựa trên hai 'ngôi sao' đình đám nhất hiện nay: Radix UI: 'Phù thủy' xử lý mọi logic phức tạp, khả năng truy cập (accessibility), và hành vi 'ngầm' của component (ví dụ: trạng thái dropdown, lớp phủ dialog, các thuộc tính ARIA). <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/radix_ui_logo_only.png' alt='Radix UI logo'> Tailwind CSS: 'Ông trùm' về styling, mang đến phương pháp utility-first cực kỳ dễ đọc và sửa đổi. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/tailwind_css_logo_only.png' alt='Tailwind CSS logo'> Vậy tại sao cái 'chiêu' copy-paste này lại 'cách mạng' đến vậy? Tất cả gói gọn trong một từ khóa: QUYỀN SỞ HỮU! 1. Bạn là 'Chủ' của Code! Vì code của component nằm ngay trong codebase của bạn, bạn có 100% quyền kiểm soát! Muốn thay đổi hiệu ứng hover của cái nút Button ư? Cứ mở button.tsx ra và chỉnh sửa mấy class Tailwind thôi. Cần thêm một biến thể (variant) mới? Làm trực tiếp luôn! Không có 'hộp đen' nào ở đây cả. Bạn không còn là 'người tiêu dùng' component nữa; bạn chính là 'ông chủ' của nó! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ShadcnOwnsCode.png' alt='Lập trình viên làm chủ code'> 2. Không Tải Nặng Thời Gian Chạy! Vì Shadcn/ui không phải là một thư viện bạn phải bundle kèm, nên nó không làm tăng thêm 'cân nặng' cho ứng dụng của bạn. Đoạn code duy nhất được 'ship' ra trình duyệt là đoạn code bạn thực sự dùng, bởi vì nó đã là một phần của mã nguồn ứng dụng rồi. 3. Thiết Kế Đẹp Mắt, Tùy Biến Vô Hạn! Các component trông cực kỳ 'xịn sò' ngay từ đầu, với thiết kế tinh tế và thẩm mỹ cao. Nhưng vì bạn có mã nguồn, bạn có thể dễ dàng chỉnh sửa chúng để phù hợp với thương hiệu công ty hay một hệ thống thiết kế độc đáo mà không cần phải 'vật lộn' với các quy tắc của thư viện nữa. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ShadcnCustomization.png' alt='Các component Shadcn/ui dễ dàng tùy biến'> 4. Trải Nghiệm Phát Triển Tuyệt Vời (DX)! Cái CLI của Shadcn/ui dùng 'sướng' cực kỳ! Thiết lập đơn giản, thêm component thì 'dễ như ăn kẹo'. Nó tự động hóa những phần 'nhàm chán' (tạo file, cài đặt Radix UI nếu cần) và dành lại phần 'vui vẻ' (xây dựng và tùy chỉnh) cho bạn. Bắt đầu với Shadcn/ui thì 'easy' không tưởng! Bước 1: Khởi tạo 'công trình' Trong dự án React/Next.js của bạn, chạy lệnh 'init': npx shadcn-ui@latest init Lệnh này sẽ hỏi bạn vài câu hỏi cấu hình (kiểu như bạn muốn lưu component ở đâu), và tạo ra một file components.json để 'ghi nhớ' các cài đặt của bạn. Nó cũng thêm một file lib/utils.ts với hàm cn siêu tiện lợi để 'hợp nhất' các class Tailwind. Bước 2: Thêm một component Giả sử chúng ta muốn thêm một component Alert: npx shadcn-ui@latest add alert Lệnh này sẽ làm gì? Kiểm tra xem bạn có các dependency cần thiết chưa (như tailwind-variants, lucide-react). Tạo file alert.tsx trong thư mục component của bạn. Bước 3: Dùng component thôi! Giờ thì bạn có thể import và sử dụng nó như bất kỳ component 'nhà làm' nào khác: ```jsximport { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert';import { Terminal } from 'lucide-react';export default function MyPage() { return ( <Alert> <Terminal className='h-4 w-4' /> <AlertTitle>Heads up!</AlertTitle> <AlertDescription> Bạn giờ đã có thể thêm component vào app bằng CLI rồi đó! </AlertDescription> </Alert> );}``` Bước 4: 'Múa cọ' tùy chỉnh! Không ưng màu viền? Mở alert.tsx, tìm cái class border và đổi nó thôi! Đơn giản vậy đó. Bạn đang chỉnh sửa chính đoạn code mà bạn đã hiểu. Thật là 'sướng' đúng không? Shadcn/ui là một công cụ 'đỉnh của chóp', nhưng không phải lúc nào cũng là 'chân ái' cho mọi trường hợp đâu nhé. Bạn chắc chắn nên cân nhắc Shadcn/ui nếu: Bạn đang bắt đầu một dự án mới tinh với React và Tailwind CSS. Bạn muốn 'toàn quyền sinh sát' về giao diện, cảm giác, và chức năng của component. Bạn đang xây dựng một hệ thống thiết kế 'độc quyền' và muốn một nền tảng vững chắc, có khả năng truy cập tốt. Bạn 'ghét cay ghét đắng' việc phải 'đấu tranh' với các quy tắc styling của thư viện bên thứ ba. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ShadcnSuitedDev.png' alt='Lập trình viên phù hợp với Shadcn/ui'> Bạn có thể muốn 'trung thành' với các thư viện truyền thống hơn nếu: Dự án của bạn không dùng Tailwind CSS. Bạn chỉ cần xây dựng một bản prototype thật nhanh và không quan tâm đến việc tùy chỉnh sâu. Cái ý tưởng 'sở hữu và bảo trì' code component nghe có vẻ 'quá sức' (dù CLI cũng có lệnh diff để giúp bạn cập nhật dễ dàng hơn!). Shadcn/ui đại diện cho một sự thay đổi mạnh mẽ trong cách chúng ta tư duy về việc xây dựng giao diện người dùng. Nó 'kéo' chúng ta ra khỏi những thư viện 'đóng hộp', 'một kích cỡ cho tất cả' và hướng tới một cách tiếp cận minh bạch hơn, có khả năng kết hợp cao hơn và 'lấy lập trình viên làm trung tâm' hơn. Bằng cách cung cấp cho bạn những 'viên gạch' được xây dựng tốt cùng với 'chìa khóa' để mở cửa code, nó giúp bạn xây dựng nhanh hơn mà không phải hy sinh chất lượng hay quyền kiểm soát. Đó là một sự kết hợp 'thiên tài' giữa quy ước và cấu hình, và nó đã xứng đáng trở thành một công cụ 'đinh' cho các nhà phát triển frontend hiện đại. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ShadcnModernUI.png' alt='Shadcn/ui - Tư duy phát triển UI mới'> Nếu bạn chưa thử, hãy 'xắn tay áo' lên, tạo một dự án mới và thêm vài component xem sao. Biết đâu bạn lại 'yêu lại từ đầu' cái cảm giác xây dựng UI thì sao!

Lê Lân profile pictureLê Lân
Unicorn Club: Từ File UX Lộn Xộn Đến AI Hỗ Trợ Thiết Kế - Tất Cả Có Trong Tuần Này!

Unicorn Club: Từ File UX Lộn Xộn Đến AI Hỗ Trợ Thiết Kế - Tất Cả Có Trong Tuần Này!

Bản tin Unicorn Club tuần này khám phá các chủ đề nóng hổi như cách sắp xếp file UX lộn xộn, kỹ thuật ẩn các phần tử phụ thuộc JavaScript mà không cần JS, và thiết kế UI do AI hỗ trợ trong Figma. Ngoài ra, còn có những kiến thức cơ bản về front-end dễ tiếp cận, phân tích tâm lý thông tin sai lệch trên mạng xã hội, và các mẹo tinh chỉnh kiểu chữ.

Lê Lân profile pictureLê Lân
:has() trong CSS: Siêu Năng Lực Mới Giúp Bạn Định Dạng Website Bá Đạo!

:has() trong CSS: Siêu Năng Lực Mới Giúp Bạn Định Dạng Website Bá Đạo!

Chào bạn! Bạn có biết CSS giờ đây có một "siêu năng lực" mới toanh, giúp chúng ta định dạng website theo cách mà trước đây chỉ có JavaScript mới làm được không? Đó chính là pseudo-class `:has()` - một "kẻ thay đổi cuộc chơi" thực sự mà dân dev chúng ta đã mong mỏi bấy lâu nay! Tưởng tượng bạn có thể điều khiển phong cách của một "ngôi nhà" chỉ vì bên trong nó có một "căn phòng" đặc biệt nào đó? Chính xác là vậy! `:has()` cho phép bạn định dạng một phần tử (cha) dựa trên sự hiện diện của các phần tử con (con, cháu, chắt... đời nào cũng được!) bên trong nó. Cùng tìm hiểu xem "siêu năng lực" này hoạt động thế nào, ứng dụng ra sao và có gì cần lưu ý nhé! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/HqP3m1D.png' alt='CSS has pseudo-class as a superpower'> Thế nào là `:has()`? Đơn giản mà nói, `:has()` là một "bộ chọn quan hệ" (relational selector). Nó sẽ nhắm mục tiêu vào một phần tử *nếu* phần tử đó chứa ít nhất một hậu duệ (con, cháu, chắt...) phù hợp với bộ chọn mà bạn chỉ định. Khác với mấy "anh em" kiểu `:hover` (khi rê chuột vào) hay `:first-child` (phần tử con đầu tiên), `:has()` lại quan tâm đến *nội dung* bên trong phần tử đó. Cú pháp thì dễ như ăn kẹo: `selector:has(bộ_chọn_con) { /* phong cách của bạn */ }`. Ví dụ nè: Bạn muốn tất cả các thẻ `<div>` nào có chứa ít nhất một thẻ `<p>` (đoạn văn) thì sẽ có viền màu xanh dương? Đơn giản thôi: `div:has(p) { border: 2px solid blue; }` Và bùm! Thế là mọi thẻ `<div>` nào có `<p>` bên trong sẽ tự động "nhận" cái viền xanh đẹp đẽ kia, bất kể `<p>` đó nằm sâu cỡ nào đi nữa. Sức mạnh của `:has()` chính là ở chỗ này: nó cho phép chúng ta chọn phần tử cha dựa trên con của nó – một điều mà CSS "thèm khát" bấy lâu nay! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/kYqXf8U.png' alt='CSS has() syntax and example'> Nói về độ "phủ sóng" thì yên tâm nhé! Tính đến tháng 6/2025 (dự kiến, ý là hiện tại đã rất tốt rồi), `:has()` đã nhận được sự ủng hộ nhiệt tình từ các trình duyệt hiện đại như Chrome, Firefox, Safari và Edge (từ phiên bản 105 trở lên). Em nó được coi là "sẵn sàng cho sản phẩm" trong hầu hết các dự án rồi đó! Tuy nhiên, cứ cẩn thận thì tốt hơn, bạn có thể kiểm tra thêm ở trang CanIUse để đảm bảo tương thích với các trình duyệt cũ hơn hoặc môi trường đặc thù của mình nha. Nếu có lỡ "dính" phải trình duyệt không hỗ trợ, cứ dùng "phương án dự phòng" hoặc "tăng cường dần dần" (progressive enhancement) để đảm bảo mọi thứ vẫn hoạt động mượt mà. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/A6j4y1P.png' alt='Browser support for CSS has()'> Vậy `:has()` hoạt động ra sao nhỉ? "Cô" này sẽ nhận một bộ chọn làm đối số (tức là cái bạn đặt trong ngoặc đơn ấy) và kiểm tra xem phần tử đích có chứa *ít nhất một* hậu duệ khớp với bộ chọn đó hay không. Nhớ nhé, không chỉ giới hạn ở con trực tiếp đâu, mà là *bất kỳ* hậu duệ nào, y hệt như cách `querySelectorAll` trong JavaScript vẫn làm vậy. Cùng "mổ xẻ" một chút nhé: Đối số bộ chọn: Cái bạn đặt trong ngoặc đơn của `:has()` có thể là bất kỳ bộ chọn CSS hợp lệ nào: từ tên thẻ (`p`), class (`.active`), ID (`#header`), hay thậm chí là các tổ hợp phức tạp (`a[href^="https"]`). Nhắm mục tiêu cha: Điều quan trọng là nó sẽ định dạng phần tử *cha*, chứ không phải cái phần tử con mà bạn đã khớp trong bộ chọn. Đánh giá động: Giống như các pseudo-class khác, `:has()` sẽ tự động cập nhật phong cách khi DOM thay đổi (ví dụ: khi JavaScript thêm bớt phần tử hay người dùng tương tác). Ví dụ "kinh điển" đây: ```html <section> <h2>Tiêu đề</h2> <p>Nội dung</p> </section> <section> <h2>Tiêu đề khác</h2> </section> ``` Bây giờ, nếu bạn viết: `section:has(p) { background: lightblue; }` Thì chỉ có thẻ `<section>` đầu tiên "hưởng" màu nền xanh nhạt thôi, vì nó là `section` duy nhất có chứa thẻ `<p>` bên trong. Tuyệt vời không? <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/J3zL1vM.png' alt='How CSS has() works with sections'> Thôi rồi, `:has()` chính là "chiếc đũa thần" mở ra cả một thế giới những khả năng định kiểu mà trước đây chúng ta phải vật lộn với JavaScript hay những "mẹo" CSS vòng vo tam quốc. Hãy xem vài ứng dụng "thực chiến" mà bạn có thể áp dụng ngay nhé! 1. Định kiểu container dựa vào nội dung: Bạn muốn cái "hộp" của mình trông khác đi tùy thuộc vào "đồ" bên trong nó? Đơn giản thôi! Ví dụ, làm nổi bật những bài viết có ảnh: `article:has(img) { border-left: 4px solid green; }` Thế là mọi thẻ `<article>` nào "ôm" một thẻ `<img>` sẽ có viền trái màu xanh lá cây, nhìn cái là biết ngay có ảnh, đúng không? <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/uGzXb7P.png' alt='Styling articles with images using :has()'> 2. Phản hồi xác thực biểu mẫu "xịn xò": Nâng tầm trải nghiệm người dùng (UX) của form bằng cách định kiểu các trường dựa trên trạng thái của chúng. Không cần JavaScript nữa! ```css .form-group:has(input:invalid) { border: 1px solid red; } .form-group:has(input:valid) { border: 1px solid green; } ``` Đoạn code này sẽ "phù phép" cho cái `form-group` (thẻ cha chứa input) có viền đỏ nếu input bên trong không hợp lệ, và xanh lá nếu hợp lệ. Cực kỳ trực quan mà lại không tốn một dòng JS nào! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/L1d4T1f.png' alt='Form validation with CSS :has()'> 3. Định kiểu component có điều kiện: Trong các framework component, `:has()` có thể định kiểu các "wrapper" (vỏ bọc) dựa trên trạng thái của con. Ví dụ, một cái card sẽ trông khác nếu nó có một nút "active" bên trong: `.card:has(.btn.active) { box-shadow: 0 0 10px rgba(0, 0, 255, 0.3); }` Thẻ card nào có nút `.btn.active` sẽ có bóng đổ đẹp mắt ngay! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/R3zK7yQ.png' alt='Conditional component styling with :has()'> 4. Chọn phần tử "hàng xóm" bá đạo: Dù không thay thế trực tiếp được các bộ chọn `+` (ngay kế tiếp) hay `~` (bất kỳ đâu sau đó), `:has()` vẫn có thể "giả lập" các mối quan hệ anh em phức tạp. Ví dụ, định kiểu một tiêu đề nếu phần tử ngay sau nó là một đoạn văn bản: `h2:has(+ p) { margin-bottom: 0.5em; }` Thế là mọi thẻ `<h2>` nào mà có ngay một thẻ `<p>` "kế bên" sẽ giảm lề dưới. Đỡ phải mò mẫm code JavaScript nhé! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/C3aV8iE.png' alt='Adjacent sibling selection using :has()'> 5. Nâng cao khả năng tiếp cận (Accessibility): Hãy dùng `:has()` để cải thiện khả năng tiếp cận bằng cách làm nổi bật các phần có nội dung đặc biệt, như cảnh báo chẳng hạn: `.section:has(.alert) { outline: 2px dashed orange; }` Mấy cái `section` nào có chứa `alert` sẽ được "vẽ" một đường viền nét đứt màu cam nổi bật, giúp người dùng dễ dàng chú ý hơn. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/vH1W2jX.png' alt='Accessibility enhancement with :has()'> Sức mạnh thật sự của `:has()` chỉ bùng nổ khi nó được kết hợp với các bộ chọn CSS khác! Cùng xem vài ví dụ nâng cao nhé! 1. `:has()` lồng nhau (Nested :has()): Bạn có thể "nhét" `:has()` vào trong `:has()` để kiểm soát chi tiết hơn: `article:has(> header:has(.featured)) { background: #f0f0f0; }` Nghĩa là: Tìm thẻ `<article>` nào mà *trực tiếp* có một thẻ `<header>` là con, và cái `<header>` đó lại chứa một phần tử có class `.featured`. Nghe có vẻ hack não nhưng lại siêu hiệu quả! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/Q2jN0kC.png' alt='Nested CSS :has() selectors'> 2. Phủ định với `:not()`: Kết hợp `:has()` với `:not()` để định kiểu các phần tử *không* có hậu duệ cụ thể: `div:not(:has(p)) { color: gray; }` Thế là mọi thẻ `<div>` nào mà "không có đứa con" `<p>` nào sẽ bị "xám xịt" lại. Độc đáo chưa? <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/Z4wL0xU.png' alt='CSS :has() with :not() for negation'> 3. Pseudo-class và Trạng thái: Kết hợp `:has()` với các pseudo-class dựa trên trạng thái như `:hover` (khi rê chuột) hay `:focus-within` (khi có phần tử con được focus): `nav:has(a:hover) { background: #eee; }` Bạn rê chuột vào bất kỳ liên kết `<a>` nào trong thanh điều hướng `<nav>`? Cả thanh `nav` sẽ tự động đổi màu nền! <video controls src='https://www.youtube.com/embed/PjRkUv-J3eU'></video> Tuy `:has()` mạnh mẽ thật đấy, nhưng không có bữa trưa nào miễn phí đâu nha! Vì nó phải "rà soát" các hậu duệ, nên đôi khi nó có thể "ngốn" tài nguyên tính toán kha khá, đặc biệt là với các DOM phức tạp hoặc bộ chọn lồng nhau sâu. Đây là vài mẹo để tối ưu hiệu suất nè: Giữ bộ chọn đơn giản: Tránh các bộ chọn quá cụ thể hoặc lồng sâu bên trong `:has()`. Ví dụ, `div:has(.btn)` sẽ nhanh hơn nhiều so với `div:has(section > div > .btn)`. Giới hạn phạm vi: Chỉ áp dụng `:has()` cho các container cụ thể thay vì dùng bộ chọn tổng quát `*:has(...)`. Kiểm tra trên các trang lớn: Với những trang có hàng ngàn phần tử, hãy dùng công cụ nhà phát triển của trình duyệt để kiểm tra hiệu suất render, xem có bị chậm ở đâu không nhé. Trong hầu hết các trường hợp, các trình duyệt hiện đại xử lý `:has()` rất hiệu quả, nhưng cứ kiểm tra trên môi trường đích của bạn để chắc chắn nhất. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/K1zW4pJ.png' alt='Performance considerations for CSS :has()'> Mặc dù "đa năng" là thế, `:has()` vẫn có vài "tật" và giới hạn bạn cần để tâm đấy! 1. Không tự tham chiếu (No Self-Referencing): Bạn không thể dùng `:has()` để chọn chính phần tử đó dựa trên chính nó (kiểu như `a:has(a)` sẽ không hoạt động như bạn nghĩ đâu). Nó được thiết kế để nhắm vào các hậu duệ, chứ không phải bản thân phần tử đó. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/N5xX2xJ.png' alt='No self-referencing in CSS :has()'> 2. Ảnh hưởng đến độ đặc hiệu (Specificity Impact): Bộ chọn bên trong `:has()` sẽ *đóng góp* vào độ đặc hiệu tổng thể của quy tắc. Ví dụ, `div:has(#unique)` sẽ có độ đặc hiệu cao hơn `div:has(p)` vì có bộ chọn ID. Hãy nhớ điều này khi bạn cần ghi đè các phong cách nhé! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/P0tY1oQ.png' alt='Specificity impact of CSS :has()'> 3. Thử thách với DOM động (Dynamic DOM Challenges): Nếu JavaScript thêm hoặc bớt các phần tử, `:has()` sẽ tự động cập nhật phong cách, nhưng việc thay đổi DOM liên tục có thể gây ra "reflows" (việc trình duyệt phải tính toán lại bố cục). Hạn chế các thay đổi không cần thiết để có hiệu suất mượt mà nha. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/Q7M0Z5z.png' alt='Dynamic DOM challenges with :has()'> 4. Không phải "bộ chọn cha" toàn năng: Dù `:has()` hay được gọi là "parent selector," nhưng nó chỉ định kiểu cho phần tử *được khớp* (tức là phần tử cha), chứ không trực tiếp định kiểu cho các con của nó. Để định kiểu các phần tử con, bạn sẽ cần thêm các bộ chọn khác: `div:has(.error) p { color: red; }` Đây là ví dụ: nếu một `div` có class `.error` bên trong, thì các thẻ `p` trong `div` đó sẽ có màu đỏ. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/U8yZ4kC.png' alt='CSS :has() is not a full parent selector'> 5. Phương án dự phòng cho trình duyệt (Browser Fallbacks): Đối với các trình duyệt không hỗ trợ, hãy cung cấp các phương án dự phòng bằng cách sử dụng "feature queries" (`@supports`) hoặc các phong cách thay thế: ```css /* Phương án dự phòng */ div { border: 1px solid gray; } /* Trình duyệt hiện đại */ @supports selector(:has(*)) { div:has(p) { border: 1px solid blue; } } ``` Đây là cách đảm bảo trải nghiệm tốt nhất trên mọi trình duyệt. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/X9u7c1O.png' alt='Browser fallbacks for CSS :has()'> Để tận dụng tối đa `:has()`, hãy "bỏ túi" những "bí kíp" sau đây: Sử dụng có chừng mực: Chỉ dùng `:has()` khi các bộ chọn đơn giản hơn (như `>`, `+`, hoặc `~`) không đủ "công lực" giải quyết vấn đề. Kết hợp với CSS hiện đại: "Cặp đôi hoàn hảo" `:has()` với các biến CSS (custom properties), container queries, hoặc các tiến bộ CSS khác để code của bạn dễ bảo trì hơn. Kiểm tra khả năng tiếp cận: Đảm bảo rằng các phong cách áp dụng với `:has()` không gây hại đến khả năng tiếp cận, ví dụ như che khuất nội dung hoặc làm giảm độ tương phản. Ghi chú cho bộ chọn phức tạp: Vì `:has()` có thể tạo ra các quy tắc "rắc rối," hãy thêm bình luận để giải thích mục đích cho những người bảo trì code sau này (hoặc chính bạn của tương lai!). Theo dõi hiệu suất: Trong các dự án quy mô lớn, hãy đo lường hiệu suất sử dụng `:has()` để tránh "nút thắt cổ chai" trong quá trình render. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/B7yT2xR.png' alt='Best practices for using CSS :has()'> Hãy tưởng tượng một bố cục blog nơi các bài viết có video nhúng cần được làm nổi bật: ```html <article> <h2>Bài viết 1</h2> <p>Nội dung văn bản</p> </article> <article> <h2>Bài viết 2</h2> <video src="example.mp4"></video> </article> ``` Bây giờ, áp dụng CSS này: ```css article:has(video) { padding: 1em; background: linear-gradient(to right, #f0f4ff, #ffffff); border-radius: 8px; } ``` Đoạn code này chỉ định kiểu cho thẻ `<article>` thứ hai (có video), tạo ra một hiệu ứng trực quan khác biệt mà không cần "động chạm" gì đến JavaScript! Đơn giản mà hiệu quả phải không? <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/Y1gX2jQ.png' alt='Real-world example of CSS :has()'> Tóm lại, pseudo-class `:has()` là một "cú hích" mang tính cách mạng cho CSS, giúp các nhà phát triển viết mã kiểu dáng trực quan và linh hoạt hơn rất nhiều. Bằng cách nhắm mục tiêu các phần tử dựa trên hậu duệ của chúng, nó đã lấp đầy một khoảng trống tồn tại bấy lâu trong khả năng của CSS, giảm bớt sự phụ thuộc vào JavaScript cho các định kiểu động. Từ xác thực biểu mẫu đến bố cục có điều kiện, các trường hợp sử dụng của nó cực kỳ đa dạng. Tuy nhiên, bạn cũng cần sử dụng nó một cách cẩn trọng để tránh các vấn đề về hiệu suất hay "cuộc chiến độ đặc hiệu" nhé! Khi các trình duyệt ngày càng hỗ trợ vững chắc, `:has()` chắc chắn sẽ trở thành một "công cụ chủ lực" trong phát triển web hiện đại. Hãy thử nghiệm nó trong dự án tiếp theo của bạn, và bạn sẽ thấy nó là một trợ thủ đắc lực không thể thiếu để tạo ra những phong cách đẹp mắt và dễ bảo trì!

Lê Lân profile pictureLê Lân
Bộ ba siêu đẳng: Flexbox, Grid và Container Queries - Khi nào dùng cái nào?

Bộ ba siêu đẳng: Flexbox, Grid và Container Queries - Khi nào dùng cái nào?

Chào bạn! Bạn có để ý dạo này màn hình lớn bé đủ cỡ không? Từ cái smartwatch nhỏ xíu đến màn hình TV siêu to khổng lồ, website của chúng ta phải làm sao để hiển thị đẹp lung linh trên mọi thiết bị đây? Đây chính là lúc "thiết kế responsive" (đáp ứng) lên ngôi, mà giờ thì không còn là "sang chảnh" nữa, nó là điều BẮT BUỘC rồi! Với vai trò là một "phù thủy" frontend, việc tạo ra những layout "co giãn" mượt mà như kẹo kéo chính là món "đinh" bạn phải nắm vững. Nhưng mà, giữa một rừng các kỹ thuật layout, bạn nên dùng chiêu nào đây? CSS Grid, Flexbox, hay tân binh "nhí" nhưng cực kỳ lợi hại - Container Queries? Đừng lo, trong bài viết này, chúng ta sẽ cùng nhau khám phá từng "chiến binh" một, xem điểm mạnh, điểm yếu của họ là gì, và lúc nào thì họ "tỏa sáng" nhất nhé. Bật mí nè, đôi khi kết hợp cả ba lại còn đỉnh hơn nữa đó!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/responsive_design_devices.png' alt='Thiết kế responsive trên nhiều thiết bị'>🎯 1. CSS Flexbox: "Vua" Căn Chỉnh Một Chiều (1D)Bạn có bao giờ cần xếp hàng các món đồ một cách ngay ngắn không? CSS Flexbox chính là "quản đốc" siêu xịn sò giúp bạn làm điều đó! Nó "chuyên trị" việc sắp xếp các phần tử theo một chiều duy nhất: hoặc là một hàng ngang tăm tắp, hoặc là một cột dọc thẳng đứng.✅ Điểm cộng: Siêu trực quan cho thanh điều hướng (nav bars), các ô trong form, hay các "thẻ" (cards) nhỏ xinh. Bạn muốn căn chỉnh khoảng cách, vị trí? Cứ giao cho Flexbox! Các thuộc tính như `gap`, `justify-content`, và `align-items` cứ gọi là "thần thánh" luôn!🛑 Hạn chế: Nếu bạn muốn xây dựng một bố cục cả trang phức tạp với nhiều hàng, nhiều cột đan xen thì Flexbox lại không phải là lựa chọn tối ưu đâu nha. Nó sinh ra để làm việc trên một "đường thẳng" mà!💡 Ví dụ 'sáng chói': Thanh điều hướng ngang.`nav { display: flex; justify-content: space-between; align-items: center; }`<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/FlexboxIllustration.jpg' alt='CSS Flexbox minh họa bố cục một chiều'>🧱 2. CSS Grid: "Kiến Trúc Sư" Đa Năng Cho Bố Cục Hai Chiều (2D)Nếu Flexbox giống "quản đốc" xếp hàng, thì CSS Grid chính là "kiến trúc sư" chuyên nghiệp, giúp bạn xây dựng cả một "mặt bằng" với đầy đủ hàng và cột cùng lúc! Nghĩ đến một bàn cờ hay một bảng tính Excel mà xem, Grid "xử" ngon ơ những bố cục phức tạp, "phức tạp" đến đâu cũng chiều!✅ Điểm cộng: Kiểm soát cực kỳ chính xác cấu trúc layout. Cho phép bạn đặt tên các đường kẻ, chia "khu vực" (grid areas) riêng biệt, và linh hoạt với hàng/cột tự động hoặc khai báo rõ ràng. Code của bạn cũng sẽ "sạch" hơn nhiều so với việc lồng Flexbox vào Flexbox rối rắm.🛑 Hạn chế: Ban đầu, việc "set up" Grid có thể hơi "lằng nhằng" một chút xíu. Và nếu bạn chỉ cần sắp xếp vài ba món đơn giản thôi thì dùng Grid có khi lại như "dùng dao mổ trâu giết gà" đó!💡 Ví dụ 'sáng chói': Bố cục 3 cột 'đáp ứng'.`grid-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; }`<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/GridIllustration.jpg' alt='CSS Grid minh họa bố cục hai chiều'>🔍 3. Container Queries: Tương Lai của Các Thành Phần "Thông Minh"Hãy quên Media Queries đi! (À mà đừng quên hẳn nhé, vẫn cần đó!). Nhưng Container Queries còn "ảo diệu" hơn nhiều! Thay vì thay đổi style dựa trên kích thước của toàn bộ màn hình (viewport), Container Queries lại cho phép bạn thay đổi style dựa trên kích thước của CHÍNH CÁI HỘP ĐANG CHỨA NÓ (container). Nghe đã thấy "thông minh" rồi đúng không? Nó giống như mỗi component có "não" riêng vậy đó!✅ Điểm cộng: Giúp bạn tạo ra những component thực sự "tái sử dụng" được và tự động "responsive" ở mọi nơi. Cực kỳ hoàn hảo cho các hệ thống thiết kế (Design Systems) lớn, nơi mà một component phải hiển thị khác nhau tùy theo không gian nó được đặt vào. Nó giải quyết triệt để vấn đề "component đẹp ở đây nhưng sang chỗ khác thì vỡ layout" kinh điển!🛑 Hạn chế: Dù giờ đây trình duyệt đã hỗ trợ cực kỳ tốt rồi, nhưng việc sử dụng nó vẫn cần chút setup ban đầu như phải khai báo `contain` và `container-type`.💡 Ví dụ 'sáng chói': Một cái thẻ (card) tự đổi layout khi kích thước "hộp" chứa nó thay đổi.`.card { container-type: inline-size; } @container (min-width: 500px) { .card { flex-direction: row; } }`<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ContainerQueryIllustration.jpg' alt='Container Queries giúp component tự điều chỉnh theo container'>🛠️ Khi Nào Dùng "Ông" Nào?Vậy tóm lại, khi nào thì "triệu hồi" vị "siêu anh hùng" nào đây?* **Thanh điều hướng (Nav bars), căn chỉnh form, các phần tử trong thẻ nhỏ (cards) theo một hàng/cột:** Gọi ngay **Flexbox** ra, nó là số 1!* **Tạo bố cục tổng thể cho trang web, gallery ảnh, hay các component cần chia thành nhiều hàng và cột phức tạp:** **CSS Grid** chính là "đũa thần" của bạn!* **Xây dựng các component "thông minh" tự động "thay hình đổi dạng" tùy theo kích thước của chính "hộp" chứa nó, hoặc phát triển hệ thống thiết kế lớn:** Đã đến lúc "triệu hồi" **Container Queries** rồi đó!Thường thì, chúng ta sẽ dùng **Grid** để dàn trang chính, tạo khung sườn tổng thể. Sau đó, trong từng "khu vực" của Grid (như từng section hay từng card), chúng ta lại dùng **Flexbox** để sắp xếp các phần tử nhỏ hơn bên trong. Còn **Container Queries** thì như một "lớp áo" linh hoạt, giúp mỗi component tự nó "biến hóa" cho phù hợp với không gian riêng mà không cần biết màn hình to hay nhỏ! Hiểu và biết cách kết hợp nhuần nhuyễn bộ ba "sát thủ" này chính là bí quyết để bạn xây dựng nên những website không chỉ đẹp mà còn "đáp ứng" và dễ bảo trì nữa đó!✍️ Bạn thì sao?Bạn đã thử "nghịch" Container Queries trong các dự án của mình chưa? Còn thách thức layout nào mà bạn vẫn đang "đau đầu" không? Chia sẻ ở phần bình luận bên dưới nhé, chúng ta cùng "mổ xẻ"!

Lê Lân profile pictureLê Lân
Chrome 135: Tạo Carousel & Slider Hoàn Hảo Chỉ Với CSS (Không Cần JavaScript!)

Chrome 135: Tạo Carousel & Slider Hoàn Hảo Chỉ Với CSS (Không Cần JavaScript!)

Chào các bạn developer! Bạn có đang “đau đầu” với việc tạo carousel và slider trên web mà không muốn dính dáng gì đến JavaScript rườm rà không? Tin vui đây! Chrome 135 vừa “bung lụa” hai thứ siêu xịn sò là pseudo-elements `::scroll-button()` và `::scroll-marker()`, hứa hẹn sẽ thay đổi cuộc chơi đấy! Thật vậy, giờ đây bạn có thể tạo ra những trải nghiệm cuộn tương tác, dễ tùy biến và thân thiện với bàn phím — tất cả chỉ từ file CSS của mình mà thôi! Vừa hiệu quả, vừa “sạch sẽ” code. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/no_js_carousel.png' alt='Carousel và slider không cần JavaScript'> Vậy, `::scroll-button()` và `::scroll-marker()` là gì mà “hot” thế? <br/><br/> **`::scroll-button()`: Nút điều khiển cuộn “thần thánh”** Tưởng tượng xem, bạn muốn có những cái nút “next” hay “previous” cho carousel của mình mà không cần viết một dòng JS nào? `::scroll-button()` chính là “người hùng” mà bạn đang tìm kiếm! Nó tự động tạo ra các nút điều khiển cuộn (kiểu mũi tên trái/phải, hoặc lên/xuống tùy hướng cuộn). Điều tuyệt vời là bạn có thể “tô vẽ” cho chúng đẹp lung linh bằng CSS y hệt như các phần tử HTML bình thường, mà người dùng vẫn có thể “nhấn nhấn” vào ngay lập tức mà không cần bạn phải code thêm gì cả. Thật tiện lợi phải không nào? <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/scroll_button_concept.png' alt='Giải thích ::scroll-button()'> <br/><br/> **`::scroll-marker()`: Những chấm báo hiệu vị trí cực “xịn”** Còn `::scroll-marker()` thì sao? Hãy nghĩ nó như mấy cái “chấm radio” bé bé xinh xinh mà bạn hay thấy dưới mấy cái slider ảnh ấy. Mỗi chấm là một “điểm dừng” (scroll snap point) trong danh sách cuộn của bạn. Chúng không chỉ đẹp mắt mà còn cực kỳ hữu ích, giúp người dùng biết mình đang ở vị trí nào và có thể tương tác trực tiếp để “nhảy cóc” đến các mục khác. Vừa trực quan, vừa thân thiện với người dùng. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/scroll_marker_concept.png' alt='Giải thích ::scroll-marker()'> <br/><br/> **Tại sao chúng lại là “big deal”?** Tại sao hai “siêu nhân” này lại được mong chờ đến vậy? Đơn giản là vì chúng mang lại một loạt lợi ích “khủng bố”: <ul><li>✅ **Tự động hỗ trợ người dùng (Accessibility):** Từ nay, carousel của bạn sẽ tự động “thân thiện” với người dùng khiếm thị (đọc màn hình) và hỗ trợ điều hướng bằng bàn phím mà không cần bạn phải “đau đầu” với các thuộc tính ARIA phức tạp nữa.</li><li>✅ **Đi kèm với `scroll-snap-type`:** Chúng sinh ra là để dành cho nhau! Khi kết hợp với `scroll-snap-type`, bạn sẽ có những trải nghiệm cuộn mượt mà, “dừng đúng điểm” mà không tốn công sức.</li><li>✅ **Nói KHÔNG với JavaScript:** Nghe có vẻ điên rồ phải không? Đúng vậy, bạn có thể tạo ra những carousel, slider hoàn chỉnh chỉ bằng CSS! Không cần JS, không cần thư viện bên thứ ba rườm rà.</li><li>✅ **Tùy biến “tẹt ga”:** Mọi thứ đều có thể tùy chỉnh bằng CSS, từ màu sắc, kích thước, hình dạng cho đến vị trí. Bạn cứ thoải mái sáng tạo!</li></ul> <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/css_power_icons.png' alt='Lợi ích của CSS pseudo-elements: không JS, dễ tiếp cận, hiệu suất'> <br/><br/> **Thử xem “phép thuật” Pure CSS này hoạt động ra sao nhé!** Trước đây, để có một cái carousel “xịn sò”, chúng ta phải dùng JavaScript để điều khiển từng chút một. Nhưng với Chrome 135, mọi thứ sẽ đơn giản hơn rất nhiều. Hãy nhìn đoạn code “thần thánh” này mà xem: ```css .scroll-container::scroll-button(start) { content: '◀'; /* Biến nó thành mũi tên lùi */ position: absolute; left: 0; top: 50%; transform: translateY(-50%); } .scroll-container::scroll-button(end) { content: '▶'; /* Biến nó thành mũi tên tiến */ position: absolute; right: 0; top: 50%; transform: translateY(-50%); } ``` Thấy chưa? Không một dòng JavaScript nào! Không cần lắng nghe sự kiện (event listeners) nào cả! Chỉ cần bạn có một container dùng `scroll-snap`, trình duyệt sẽ tự động “hô biến” mọi thứ cho bạn. Thật vi diệu! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/pure_css_carousel_code.png' alt='Ví dụ code CSS tạo nút điều khiển cuộn'> <br/><br/> **Tại sao điều này lại quan trọng?** Vậy tại sao những tính năng này lại “khủng khiếp” đến vậy? <ul><li>✅ **Accessibility “chuẩn chỉnh”:** Không còn phải vật lộn với ARIA nữa. Mọi thứ được trình duyệt xử lý “tận gốc”.</li><li>✅ **Hỗ trợ bàn phím tích hợp:** Người dùng có thể dễ dàng điều hướng bằng bàn phím mà không cần bạn phải code thêm.</li><li>✅ **Không cần JS cho slider:** Cắt giảm sự phụ thuộc vào JavaScript, giúp code của bạn nhẹ nhàng hơn.</li><li>✅ **Kích thước file nhỏ hơn, hiệu năng tốt hơn:** Ít JS hơn đồng nghĩa với việc trang web của bạn tải nhanh hơn, mượt mà hơn. Ai mà chẳng thích website load “nhanh như chớp” chứ?</li></ul> <br/><br/> **Tình hình hỗ trợ trình duyệt (tính đến Chrome 135):** À mà khoan, tin tốt thì có nhưng cũng cần lưu ý một chút về “tình hình chiến sự” trình duyệt nhé: <ul><li>✅ **Chrome 135+:** Đã có thể “quẩy” được rồi!</li><li>⚠️ **Edge (Chromium):** Đang trong giai đoạn thử nghiệm (cần bật flag).</li><li>❌ **Firefox & Safari:** Vẫn còn đang “ngủ đông” (chưa hỗ trợ).</li></ul> Để bật tính năng này trên Chrome (nếu bạn dùng bản dưới 135 hoặc muốn chắc chắn): gõ `chrome://flags/#enable-experimental-web-platform-features` vào thanh địa chỉ và bật nó lên nha! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/browser_support_scroll.png' alt='Tình trạng hỗ trợ trình duyệt cho ::scroll-button và ::scroll-marker'> <br/><br/> **Lời kết:** Đây thực sự là một bước nhảy vọt lớn cho các thành phần web gốc! Một khi Firefox và Safari “tỉnh giấc” và hỗ trợ, chúng ta sẽ có những carousel và slider hoàn toàn tự nhiên, thân thiện với mọi người mà không cần đến những thư viện cồng kềnh hay các “thủ thuật” JavaScript phức tạp nữa. Cho đến lúc đó, hãy nhớ nguyên tắc “progressive enhancement” (nâng cao dần) nhé – nghĩa là hãy đảm bảo trang web của bạn vẫn hoạt động tốt trên các trình duyệt cũ, sau đó mới thêm các tính năng mới cho những trình duyệt hiện đại hơn. Bạn sẽ “chế tạo” gì với `::scroll-button()` và `::scroll-marker()`? Một carousel portfolio cực chất? Một slider sản phẩm lung linh? Hay một hướng dẫn onboarding siêu mượt? Liệu các developer có nên “từ bỏ” JS slider mãi mãi không? Hay vẫn còn quá sớm để nói? Hãy thử ngay bản demo carousel phản hồi đầy đủ tại đây để xem “phép thuật” này hoạt động ra sao: [https://clever-cocada-9cd821.netlify.app/](https://clever-cocada-9cd821.netlify.app/) Đừng quên theo dõi mình để không bỏ lỡ những “mẹo hay ho” về frontend nhé!

Lê Lân profile pictureLê Lân
Unicorn Club Weekly: Design, CSS, UX, & More for Devs & Designers!

Unicorn Club Weekly: Design, CSS, UX, & More for Devs & Designers!

Khám phá bản tin hàng tuần từ Unicorn Club với Adam: nguồn cảm hứng thiết kế, thủ thuật CSS, bài viết UX sâu sắc và thông tin sự kiện mới nhất dành cho nhà phát triển front-end và nhà thiết kế UX/UI. Cập nhật các xu hướng về UX, CSS, AI và mẹo tối ưu hóa trang web.

Lê Lân profile pictureLê Lân
Blog - letranglan.top | undefined